diff --git a/sql/ddl_api.sql b/sql/ddl_api.sql index 11f04f3da..819594b79 100644 --- a/sql/ddl_api.sql +++ b/sql/ddl_api.sql @@ -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$; diff --git a/sql/ddl_internal.sql b/sql/ddl_internal.sql index 3d88a13b6..8296b42a0 100644 --- a/sql/ddl_internal.sql +++ b/sql/ddl_internal.sql @@ -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; diff --git a/sql/updates/pre-0.6.1--0.7.0.sql b/sql/updates/pre-0.6.1--0.7.0.sql index ff94daa03..9a6732ced 100644 --- a/sql/updates/pre-0.6.1--0.7.0.sql +++ b/sql/updates/pre-0.6.1--0.7.0.sql @@ -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); diff --git a/src/catalog.c b/src/catalog.c index 37e778717..9169ce15f 100644 --- a/src/catalog.c +++ b/src/catalog.c @@ -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; diff --git a/src/catalog.h b/src/catalog.h index e878c5eca..cbaa47683 100644 --- a/src/catalog.h +++ b/src/catalog.h @@ -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 */ diff --git a/src/errors.h b/src/errors.h index 74e719927..8f61d2471 100644 --- a/src/errors.h +++ b/src/errors.h @@ -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 diff --git a/src/hypertable.c b/src/hypertable.c index 0092cae99..be45391c5 100644 --- a/src/hypertable.c +++ b/src/hypertable.c @@ -1,10 +1,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #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 diff --git a/src/hypertable.h b/src/hypertable.h index 3da550505..a2924086e 100644 --- a/src/hypertable.h +++ b/src/hypertable.h @@ -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 */ diff --git a/src/process_utility.c b/src/process_utility.c index 06dcfffb9..5abeb2511 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -1594,7 +1594,7 @@ timescaledb_ddl_command_start( ProcessUtilityContext context, ParamListInfo params, #if PG10 - QueryEnvironment * queryEnv, + QueryEnvironment *queryEnv, #endif DestReceiver *dest, char *completion_tag) diff --git a/src/tablespace.c b/src/tablespace.c index abde975c8..1618a8213 100644 --- a/src/tablespace.c +++ b/src/tablespace.c @@ -1,12 +1,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #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))); +} diff --git a/src/tablespace.h b/src/tablespace.h index 14e4f41be..aa635f513 100644 --- a/src/tablespace.h +++ b/src/tablespace.h @@ -2,10 +2,11 @@ #define TIMESCALEDB_TABLESPACE_H #include +#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 */ diff --git a/test/expected/extension.out b/test/expected/extension.out index 20808b445..e3800d354 100644 --- a/test/expected/extension.out +++ b/test/expected/extension.out @@ -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) diff --git a/test/expected/pg_dump.out b/test/expected/pg_dump.out index 879843942..7d255bfce 100644 --- a/test/expected/pg_dump.out +++ b/test/expected/pg_dump.out @@ -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 diff --git a/test/expected/tablespace.out b/test/expected/tablespace.out index f2f2717f3..11c9a73fd 100644 --- a/test/expected/tablespace.out +++ b/test/expected/tablespace.out @@ -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; diff --git a/test/sql/tablespace.sql b/test/sql/tablespace.sql index e4b4aefe6..d0d31618e 100644 --- a/test/sql/tablespace.sql +++ b/test/sql/tablespace.sql @@ -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;