Add function to detach tablespaces from hypertables

Tablespaces can now be detached from hypertables using
`tablespace_detach()`. This function can either detach
a tablespace from all tables or only a specific table.

Having the ability to detach tablespace allows more
advanced storage management, for instance, one can detach
tablespaces that are running low on diskspace while attaching
new ones to replace the old ones.
This commit is contained in:
Erik Nordström 2017-11-21 16:42:51 +01:00 committed by Erik Nordström
parent e593876cb0
commit 6e92383592
15 changed files with 493 additions and 46 deletions

View File

@ -303,3 +303,17 @@ CREATE OR REPLACE FUNCTION attach_tablespace(tablespace NAME, hypertable REGCLAS
$BODY$
SELECT * FROM _timescaledb_internal.attach_tablespace(tablespace, hypertable);
$BODY$;
-- Detach the given tablespace from a hypertable
CREATE OR REPLACE FUNCTION detach_tablespace(tablespace NAME, hypertable REGCLASS = NULL)
RETURNS INTEGER LANGUAGE SQL AS
$BODY$
SELECT * FROM _timescaledb_internal.detach_tablespace(tablespace, hypertable);
$BODY$;
-- Detach all tablespaces from the a hypertable
CREATE OR REPLACE FUNCTION detach_tablespaces(hypertable REGCLASS)
RETURNS INTEGER LANGUAGE SQL AS
$BODY$
SELECT * FROM _timescaledb_internal.detach_tablespaces(hypertable);
$BODY$;

View File

@ -65,7 +65,7 @@ BEGIN
-- Create the schema for the hypertable data if needed
PERFORM _timescaledb_internal.create_hypertable_schema(associated_schema_name);
id := nextval(pg_get_serial_sequence('_timescaledb_catalog.hypertable','id'));
id := nextval(pg_get_serial_sequence('_timescaledb_catalog.hypertable','id'));
IF associated_table_prefix IS NULL THEN
associated_table_prefix = format('_hyper_%s', id);
@ -621,6 +621,12 @@ $BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.attach_tablespace(tablespace NAME, hypertable REGCLASS) RETURNS VOID
AS '$libdir/timescaledb', 'tablespace_attach' LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION _timescaledb_internal.detach_tablespace(tablespace NAME, hypertable REGCLASS) RETURNS INTEGER
AS '$libdir/timescaledb', 'tablespace_detach' LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION _timescaledb_internal.detach_tablespaces(hypertable REGCLASS) RETURNS INTEGER
AS '$libdir/timescaledb', 'tablespace_detach_all_from_hypertable' LANGUAGE C VOLATILE;
--documentation of these function located in chunk_index.h
CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_index_clone(chunk_index_oid OID) RETURNS OID
AS '$libdir/timescaledb', 'chunk_index_clone' LANGUAGE C VOLATILE STRICT;

View File

@ -35,6 +35,3 @@ DROP FUNCTION create_hypertable(regclass,name,name,integer,name,name,anyelement,
DROP FUNCTION add_dimension(regclass,name,integer,bigint);
DROP FUNCTION _timescaledb_internal.create_hypertable_row(regclass,name,name,name,name,integer,name,name,bigint,name);
DROP FUNCTION _timescaledb_internal.add_dimension(regclass,_timescaledb_catalog.hypertable,name,integer,bigint,boolean);
-- Tablespace functions
DROP FUNCTION _timescaledb_internal.attach_tablespace(integer, name);

View File

@ -467,6 +467,11 @@ catalog_delete(Relation rel, HeapTuple tuple)
catalog_delete_tid(rel, &tuple->t_self);
}
void
catalog_delete_only(Relation rel, HeapTuple tuple)
{
CatalogTupleDelete(rel, &tuple->t_self);
}
/*
* Invalidate TimescaleDB catalog caches.
@ -510,6 +515,7 @@ catalog_invalidate_cache(Oid catalog_relid, CmdType operation)
break;
case HYPERTABLE:
case DIMENSION:
case TABLESPACE:
relid = catalog_get_cache_proxy_id(catalog, CACHE_TYPE_HYPERTABLE);
CacheInvalidateRelcacheByRelid(relid);
break;

View File

@ -519,4 +519,7 @@ void catalog_delete_tid(Relation rel, ItemPointer tid);
void catalog_delete(Relation rel, HeapTuple tuple);
void catalog_invalidate_cache(Oid catalog_relid, CmdType operation);
/* Delete only: do not increment command counter or invalidate caches */
void catalog_delete_only(Relation rel, HeapTuple tuple);
#endif /* TIMESCALEDB_CATALOG_H */

View File

@ -28,6 +28,7 @@
#define ERRCODE_IO_NODE_EXISTS MAKE_SQLSTATE('I','O','1','2','0')
#define ERRCODE_IO_USER_EXISTS MAKE_SQLSTATE('I','O','1','3','0')
#define ERRCODE_IO_TABLESPACE_ALREADY_ATTACHED MAKE_SQLSTATE('I','O','1','4','0')
#define ERRCODE_IO_TABLESPACE_NOT_ATTACHED MAKE_SQLSTATE('I','O','1','5','0')
/*
--IO500 - GROUP: internal error

View File

@ -1,10 +1,13 @@
#include <postgres.h>
#include <access/htup_details.h>
#include <utils/lsyscache.h>
#include <utils/syscache.h>
#include <utils/memutils.h>
#include <utils/builtins.h>
#include <nodes/memnodes.h>
#include <catalog/namespace.h>
#include <commands/tablespace.h>
#include <miscadmin.h>
#include "hypertable.h"
#include "dimension.h"
@ -16,6 +19,48 @@
#include "scanner.h"
#include "catalog.h"
static Oid
rel_get_owner(Oid relid)
{
HeapTuple tuple;
Oid ownerid;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation with OID %u does not exist", relid)));
ownerid = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
ReleaseSysCache(tuple);
return ownerid;
}
bool
hypertable_has_privs_of(Oid hypertable_oid, Oid userid)
{
return has_privs_of_role(userid, rel_get_owner(hypertable_oid));
}
Oid
hypertable_permissions_check(Oid hypertable_oid, Oid userid)
{
Oid ownerid = rel_get_owner(hypertable_oid);
if (!has_privs_of_role(userid, ownerid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("User \"%s\" lacks permissions on table \"%s\"",
GetUserNameFromId(userid, true),
get_rel_name(hypertable_oid))));
return ownerid;
}
Hypertable *
hypertable_from_tuple(HeapTuple tuple)
{
@ -230,7 +275,31 @@ hypertable_has_tablespace(Hypertable *ht, Oid tspc_oid)
Tablespace *
hypertable_add_tablespace(Hypertable *ht, int32 tspc_id, Oid tspc_oid)
{
return tablespaces_add(ht->tablespaces, tspc_id, tspc_oid);
FormData_tablespace form = {
.id = tspc_id,
.hypertable_id = ht->fd.id,
};
namestrcpy(&form.tablespace_name, get_tablespace_name(tspc_oid));
return tablespaces_add(ht->tablespaces, &form, tspc_oid);
}
bool
hypertable_delete_tablespace(Hypertable *ht, Oid tspc_oid)
{
if (NULL == ht->tablespaces)
return false;
return tablespaces_delete(ht->tablespaces, tspc_oid);
}
int
hypertable_delete_all_tablespaces(Hypertable *ht)
{
if (NULL == ht->tablespaces)
return 0;
return tablespaces_clear(ht->tablespaces);
}
static inline Oid

View File

@ -21,6 +21,8 @@ typedef struct Hypertable
Tablespaces *tablespaces;
} Hypertable;
extern bool hypertable_has_privs_of(Oid hypertable_oid, Oid userid);
extern Oid hypertable_permissions_check(Oid hypertable_oid, Oid userid);
extern Hypertable *hypertable_from_tuple(HeapTuple tuple);
extern int hypertable_set_name(Hypertable *ht, const char *newname);
extern int hypertable_set_schema(Hypertable *ht, const char *newname);
@ -30,5 +32,7 @@ extern Oid hypertable_relid(RangeVar *rv);
extern bool is_hypertable(Oid relid);
extern bool hypertable_has_tablespace(Hypertable *ht, Oid tspc_oid);
extern Tablespace *hypertable_add_tablespace(Hypertable *ht, int32 tspc_id, Oid tspc_oid);
extern bool hypertable_delete_tablespace(Hypertable *ht, Oid tspc_id);
extern int hypertable_delete_all_tablespaces(Hypertable *ht);
#endif /* TIMESCALEDB_HYPERTABLE_H */

View File

@ -1594,7 +1594,7 @@ timescaledb_ddl_command_start(
ProcessUtilityContext context,
ParamListInfo params,
#if PG10
QueryEnvironment * queryEnv,
QueryEnvironment *queryEnv,
#endif
DestReceiver *dest,
char *completion_tag)

View File

@ -1,12 +1,12 @@
#include <postgres.h>
#include <fmgr.h>
#include <utils/lsyscache.h>
#include <utils/syscache.h>
#include <utils/spccache.h>
#include <utils/acl.h>
#include <utils/builtins.h>
#include <utils/fmgroids.h>
#include <commands/tablespace.h>
#include <access/xact.h>
#include <miscadmin.h>
#include "hypertable_cache.h"
@ -18,26 +18,6 @@
#define TABLESPACE_DEFAULT_CAPACITY 4
static Oid
rel_get_owner(Oid relid)
{
HeapTuple tuple;
Oid ownerid;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation with OID %u does not exist", relid)));
ownerid = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
ReleaseSysCache(tuple);
return ownerid;
}
static Tablespaces *
tablespaces_alloc(int capacity)
{
@ -52,7 +32,7 @@ tablespaces_alloc(int capacity)
}
Tablespace *
tablespaces_add(Tablespaces *tspcs, int32 tspc_id, Oid tspc_oid)
tablespaces_add(Tablespaces *tspcs, FormData_tablespace *form, Oid tspc_oid)
{
Tablespace *tspc;
@ -63,21 +43,51 @@ tablespaces_add(Tablespaces *tspcs, int32 tspc_id, Oid tspc_oid)
}
tspc = &tspcs->tablespaces[tspcs->num_tablespaces++];
tspc->tablespace_id = tspc_id;
memcpy(&tspc->fd, form, sizeof(FormData_tablespace));
tspc->tablespace_oid = tspc_oid;
return tspc;
}
int
tablespaces_clear(Tablespaces *tspcs)
{
int num = tspcs->num_tablespaces;
tspcs->num_tablespaces = 0;
return num;
}
bool
tablespaces_delete(Tablespaces *tspcs, Oid tspc_oid)
{
int i;
for (i = 0; i < tspcs->num_tablespaces; i++)
{
if (tspc_oid == tspcs->tablespaces[i].tablespace_oid)
{
memcpy(&tspcs->tablespaces[i],
&tspcs->tablespaces[i + 1],
sizeof(Tablespace) * (tspcs->num_tablespaces - i - 1));
tspcs->num_tablespaces--;
return true;
}
}
return false;
}
static bool
tablespace_tuple_found(TupleInfo *ti, void *data)
{
Tablespaces *tspcs = data;
FormData_tablespace *form = (FormData_tablespace *) GETSTRUCT(ti->tuple);
Oid tspcoid = get_tablespace_oid(NameStr(form->tablespace_name), true);
tablespaces_add(tspcs, form, tspcoid);
tablespaces_add(tspcs,
form->id,
get_tablespace_oid(NameStr(form->tablespace_name), true));
return true;
}
@ -141,6 +151,136 @@ tablespace_insert(int32 hypertable_id, const char *tspcname)
return id;
}
typedef struct TablespaceScanInfo
{
Catalog *catalog;
Cache *hcache;
Oid userid;
int num_filtered;
int stopcount;
} TablespaceScanInfo;
static bool
tablespace_tuple_delete(TupleInfo *ti, void *data)
{
TablespaceScanInfo *info = data;
CatalogSecurityContext sec_ctx;
catalog_become_owner(info->catalog, &sec_ctx);
catalog_delete_only(ti->scanrel, ti->tuple);
catalog_restore_user(&sec_ctx);
return (info->stopcount == 0 || ti->count < info->stopcount);
}
static int
tablespace_delete(int32 hypertable_id, const char *tspcname)
{
ScanKeyData scankey[2];
TablespaceScanInfo info = {
.catalog = catalog_get(),
.stopcount = (NULL != tspcname),
};
ScannerCtx scanctx = {
.table = info.catalog->tables[TABLESPACE].id,
.index = info.catalog->tables[TABLESPACE].index_ids[TABLESPACE_HYPERTABLE_ID_TABLESPACE_NAME_IDX],
.scantype = ScannerTypeIndex,
.nkeys = 0,
.scankey = scankey,
.tuple_found = tablespace_tuple_delete,
.data = &info,
.lockmode = RowExclusiveLock,
.scandirection = ForwardScanDirection,
};
int num_deleted;
ScanKeyInit(&scankey[scanctx.nkeys++],
Anum_tablespace_hypertable_id_tablespace_name_idx_hypertable_id,
BTEqualStrategyNumber,
F_INT4EQ,
Int32GetDatum(hypertable_id));
if (NULL != tspcname)
ScanKeyInit(&scankey[scanctx.nkeys++],
Anum_tablespace_hypertable_id_tablespace_name_idx_tablespace_name,
BTEqualStrategyNumber,
F_NAMEEQ,
DirectFunctionCall1(namein, CStringGetDatum(tspcname)));
num_deleted = scanner_scan(&scanctx);
if (num_deleted > 0)
{
catalog_invalidate_cache(catalog_table_get_id(info.catalog, TABLESPACE), CMD_DELETE);
CommandCounterIncrement();
}
return num_deleted;
}
static bool
tablespace_tuple_owner_filter(TupleInfo *ti, void *data)
{
TablespaceScanInfo *info = data;
FormData_tablespace *form = (FormData_tablespace *) GETSTRUCT(ti->tuple);
Hypertable *ht;
ht = hypertable_cache_get_entry_by_id(info->hcache, form->hypertable_id);
Assert(NULL != ht);
if (hypertable_has_privs_of(ht->main_table_relid, info->userid))
return true;
info->num_filtered++;
return false;
}
static int
tablespace_delete_from_all(const char *tspcname, Oid userid)
{
ScanKeyData scankey[1];
TablespaceScanInfo info = {
.catalog = catalog_get(),
.hcache = hypertable_cache_pin(),
.userid = userid,
};
ScannerCtx scanctx = {
.table = info.catalog->tables[TABLESPACE].id,
.scantype = ScannerTypeHeap,
.nkeys = 1,
.scankey = scankey,
.tuple_found = tablespace_tuple_delete,
.filter = tablespace_tuple_owner_filter,
.data = &info,
.lockmode = RowExclusiveLock,
.scandirection = ForwardScanDirection,
};
int num_deleted;
ScanKeyInit(&scankey[0],
Anum_tablespace_tablespace_name,
BTEqualStrategyNumber, F_NAMEEQ,
DirectFunctionCall1(namein, CStringGetDatum(tspcname)));
cache_release(info.hcache);
num_deleted = scanner_scan(&scanctx);
if (num_deleted > 0)
{
catalog_invalidate_cache(catalog_table_get_id(info.catalog, TABLESPACE), CMD_DELETE);
CommandCounterIncrement();
}
if (info.num_filtered > 0)
elog(NOTICE, "Tablespace \"%s\" remains attached to %d hypertable(s) due to lack of permissions",
tspcname, info.num_filtered);
return num_deleted;
}
TS_FUNCTION_INFO_V1(tablespace_attach);
Datum
@ -152,7 +292,6 @@ tablespace_attach(PG_FUNCTION_ARGS)
Hypertable *ht;
Oid tspc_oid;
int32 tspc_id;
Oid user_oid = GetUserId();
Oid ownerid;
AclResult aclresult;
MemoryContext old;
@ -179,13 +318,7 @@ tablespace_attach(PG_FUNCTION_ARGS)
errhint("A tablespace needs to be created"
" before attaching it to a hypertable")));
ownerid = rel_get_owner(hypertable_oid);
if (!has_privs_of_role(user_oid, ownerid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("User \"%s\" lacks permissions on table \"%s\"",
GetUserNameFromId(user_oid, true), get_rel_name(hypertable_oid))));
ownerid = hypertable_permissions_check(hypertable_oid, GetUserId());
aclresult = pg_tablespace_aclcheck(tspc_oid, ownerid, ACL_CREATE);
@ -201,7 +334,8 @@ tablespace_attach(PG_FUNCTION_ARGS)
if (NULL == ht)
ereport(ERROR,
(errcode(ERRCODE_IO_HYPERTABLE_NOT_EXIST),
errmsg("Table \"%s\" is not a hypertable", get_rel_name(hypertable_oid))));
errmsg("Table \"%s\" is not a hypertable",
get_rel_name(hypertable_oid))));
if (hypertable_has_tablespace(ht, tspc_oid))
ereport(ERROR,
@ -222,3 +356,116 @@ tablespace_attach(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
static int
tablespace_detach_one(Oid hypertable_oid, const char *tspcname, Oid tspcoid)
{
Cache *hcache;
Hypertable *ht;
int ret;
hypertable_permissions_check(hypertable_oid, GetUserId());
hcache = hypertable_cache_pin();
ht = hypertable_cache_get_entry(hcache, hypertable_oid);
if (NULL == ht)
ereport(ERROR,
(errcode(ERRCODE_IO_HYPERTABLE_NOT_EXIST),
errmsg("Table \"%s\" is not a hypertable",
get_rel_name(hypertable_oid))));
if (!hypertable_has_tablespace(ht, tspcoid))
ereport(ERROR,
(errcode(ERRCODE_IO_TABLESPACE_NOT_ATTACHED),
errmsg("Tablespace \"%s\" is not attached to hypertable \"%s\"",
tspcname, get_rel_name(hypertable_oid))));
ret = tablespace_delete(ht->fd.id, tspcname);
hypertable_delete_tablespace(ht, tspcoid);
cache_release(hcache);
return ret;
}
static int
tablespace_detach_all(Oid hypertable_oid)
{
Cache *hcache;
Hypertable *ht;
int ret;
hypertable_permissions_check(hypertable_oid, GetUserId());
hcache = hypertable_cache_pin();
ht = hypertable_cache_get_entry(hcache, hypertable_oid);
if (NULL == ht)
ereport(ERROR,
(errcode(ERRCODE_IO_HYPERTABLE_NOT_EXIST),
errmsg("Table \"%s\" is not a hypertable",
get_rel_name(hypertable_oid))));
ret = tablespace_delete(ht->fd.id, NULL);
cache_release(hcache);
return ret;
}
TS_FUNCTION_INFO_V1(tablespace_detach);
Datum
tablespace_detach(PG_FUNCTION_ARGS)
{
Oid hypertable_oid = InvalidOid;
Name tspcname;
Oid tspcoid;
int ret;
switch (PG_NARGS())
{
case 1:
tspcname = PG_GETARG_NAME(0);
break;
case 2:
tspcname = PG_GETARG_NAME(0);
hypertable_oid = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1);
break;
default:
elog(ERROR, "Invalid number of arguments");
}
if (NULL == tspcname)
elog(ERROR, "Invalid tablespace name");
tspcoid = get_tablespace_oid(NameStr(*tspcname), true);
if (!OidIsValid(tspcoid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("No tablespace \"%s\" exists.",
NameStr(*tspcname))));
if (OidIsValid(hypertable_oid))
ret = tablespace_detach_one(hypertable_oid, NameStr(*tspcname), tspcoid);
else
ret = tablespace_delete_from_all(NameStr(*tspcname), GetUserId());
PG_RETURN_INT32(ret);
}
TS_FUNCTION_INFO_V1(tablespace_detach_all_from_hypertable);
Datum
tablespace_detach_all_from_hypertable(PG_FUNCTION_ARGS)
{
if (PG_NARGS() != 1)
elog(ERROR, "Invalid number of arguments");
if (PG_ARGISNULL(0))
elog(ERROR, "Invalid argument");
PG_RETURN_INT32(tablespace_detach_all(PG_GETARG_OID(0)));
}

View File

@ -2,10 +2,11 @@
#define TIMESCALEDB_TABLESPACE_H
#include <postgres.h>
#include "catalog.h"
typedef struct Tablespace
{
int32 tablespace_id;
FormData_tablespace fd;
Oid tablespace_oid;
} Tablespace;
@ -16,7 +17,9 @@ typedef struct Tablespaces
Tablespace *tablespaces;
} Tablespaces;
extern Tablespace *tablespaces_add(Tablespaces *tablespaces, int32 tspc_id, Oid tspc_oid);
extern Tablespace *tablespaces_add(Tablespaces *tablespaces, FormData_tablespace *form, Oid tspc_oid);
extern bool tablespaces_delete(Tablespaces *tspcs, Oid tspc_oid);
extern int tablespaces_clear(Tablespaces *tspcs);
extern Tablespaces *tablespace_scan(int32 hypertable_id);
#endif /* TIMESCALEDB_TABLESPACE_H */

View File

@ -16,6 +16,8 @@ ORDER BY proname;
chunk_relation_size
chunk_relation_size_pretty
create_hypertable
detach_tablespace
detach_tablespaces
drop_chunks
first
histogram
@ -26,5 +28,5 @@ ORDER BY proname;
last
set_chunk_time_interval
time_bucket
(15 rows)
(17 rows)

View File

@ -49,7 +49,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
119
123
(1 row)
SELECT * FROM test.show_columns('public."two_Partitions"');
@ -235,7 +235,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
119
123
(1 row)
--main table and chunk schemas should be the same

View File

@ -80,6 +80,14 @@ INNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);
(2 rows)
--
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
-- User doesn't have permission on tablespace1 --> error
CREATE TABLE tspace_1dim(time timestamp, temp float, device text) TABLESPACE tablespace1;
ERROR: permission denied for tablespace tablespace1
-- Grant permission to tablespace1
SET ROLE :ROLE_DEFAULT_PERM_USER;
GRANT CREATE ON TABLESPACE tablespace1 TO :ROLE_DEFAULT_PERM_USER_2;
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
CREATE TABLE tspace_1dim(time timestamp, temp float, device text) TABLESPACE tablespace1;
SELECT create_hypertable('tspace_1dim', 'time');
NOTICE: Adding NOT NULL constraint to time column time (NULL time values not allowed)
@ -94,6 +102,15 @@ SELECT attach_tablespace('tablespace2', 'tspace_1dim');
(1 row)
SELECT * FROM _timescaledb_catalog.tablespace;
id | hypertable_id | tablespace_name
----+---------------+-----------------
1 | 1 | tablespace1
2 | 1 | tablespace2
3 | 2 | tablespace1
4 | 2 | tablespace2
(4 rows)
INSERT INTO tspace_1dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');
INSERT INTO tspace_1dim VALUES ('2017-03-20T09:00:01', 24.3, 'brown');
SELECT relname, spcname FROM pg_class c
@ -107,6 +124,55 @@ INNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);
_hyper_2_4_chunk | tablespace2
(4 rows)
--detach tablespace1 from all tables. Due to lack of permissions,
--should only detach from 'tspace_1dim' (1 tablespace)
SELECT detach_tablespace('tablespace1');
NOTICE: Tablespace "tablespace1" remains attached to 1 hypertable(s) due to lack of permissions
detach_tablespace
-------------------
1
(1 row)
SELECT * FROM _timescaledb_catalog.tablespace;
id | hypertable_id | tablespace_name
----+---------------+-----------------
1 | 1 | tablespace1
2 | 1 | tablespace2
4 | 2 | tablespace2
(3 rows)
--detach the other tablespace
SELECT detach_tablespace('tablespace2', 'tspace_1dim');
detach_tablespace
-------------------
1
(1 row)
SELECT * FROM _timescaledb_catalog.tablespace;
id | hypertable_id | tablespace_name
----+---------------+-----------------
1 | 1 | tablespace1
2 | 1 | tablespace2
(2 rows)
--detaching a tablespace from table without permissions should fail
SELECT detach_tablespace('tablespace2', 'tspace_2dim');
ERROR: User "default_perm_user_2" lacks permissions on table "tspace_2dim"
SELECT detach_tablespaces('tspace_2dim');
ERROR: User "default_perm_user_2" lacks permissions on table "tspace_2dim"
--set other user should make detach work
SET ROLE :ROLE_DEFAULT_PERM_USER;
SELECT detach_tablespaces('tspace_2dim');
detach_tablespaces
--------------------
2
(1 row)
SELECT * FROM _timescaledb_catalog.tablespace;
id | hypertable_id | tablespace_name
----+---------------+-----------------
(0 rows)
--cleanup
DROP TABLE tspace_1dim CASCADE;
DROP TABLE tspace_2dim CASCADE;

View File

@ -61,10 +61,21 @@ INNER JOIN pg_tablespace t ON (c.reltablespace = t.oid)
INNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);
--
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
-- User doesn't have permission on tablespace1 --> error
CREATE TABLE tspace_1dim(time timestamp, temp float, device text) TABLESPACE tablespace1;
-- Grant permission to tablespace1
SET ROLE :ROLE_DEFAULT_PERM_USER;
GRANT CREATE ON TABLESPACE tablespace1 TO :ROLE_DEFAULT_PERM_USER_2;
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
CREATE TABLE tspace_1dim(time timestamp, temp float, device text) TABLESPACE tablespace1;
SELECT create_hypertable('tspace_1dim', 'time');
SELECT attach_tablespace('tablespace2', 'tspace_1dim');
SELECT * FROM _timescaledb_catalog.tablespace;
INSERT INTO tspace_1dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');
INSERT INTO tspace_1dim VALUES ('2017-03-20T09:00:01', 24.3, 'brown');
@ -72,6 +83,24 @@ SELECT relname, spcname FROM pg_class c
INNER JOIN pg_tablespace t ON (c.reltablespace = t.oid)
INNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);
--detach tablespace1 from all tables. Due to lack of permissions,
--should only detach from 'tspace_1dim' (1 tablespace)
SELECT detach_tablespace('tablespace1');
SELECT * FROM _timescaledb_catalog.tablespace;
--detach the other tablespace
SELECT detach_tablespace('tablespace2', 'tspace_1dim');
SELECT * FROM _timescaledb_catalog.tablespace;
--detaching a tablespace from table without permissions should fail
SELECT detach_tablespace('tablespace2', 'tspace_2dim');
SELECT detach_tablespaces('tspace_2dim');
--set other user should make detach work
SET ROLE :ROLE_DEFAULT_PERM_USER;
SELECT detach_tablespaces('tspace_2dim');
SELECT * FROM _timescaledb_catalog.tablespace;
--cleanup
DROP TABLE tspace_1dim CASCADE;
DROP TABLE tspace_2dim CASCADE;