From 6e923835927ec841572fb2b25e498538484ac9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Nordstro=CC=88m?= Date: Tue, 21 Nov 2017 16:42:51 +0100 Subject: [PATCH] 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. --- sql/ddl_api.sql | 14 ++ sql/ddl_internal.sql | 8 +- sql/updates/pre-0.6.1--0.7.0.sql | 3 - src/catalog.c | 6 + src/catalog.h | 3 + src/errors.h | 1 + src/hypertable.c | 71 ++++++- src/hypertable.h | 4 + src/process_utility.c | 2 +- src/tablespace.c | 317 +++++++++++++++++++++++++++---- src/tablespace.h | 7 +- test/expected/extension.out | 4 +- test/expected/pg_dump.out | 4 +- test/expected/tablespace.out | 66 +++++++ test/sql/tablespace.sql | 29 +++ 15 files changed, 493 insertions(+), 46 deletions(-) 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;