From 4368fcff3c19f2291f6f0276e6942a428c794d42 Mon Sep 17 00:00:00 2001 From: niksa Date: Mon, 28 Jan 2019 12:36:44 +0100 Subject: [PATCH] Add function to reconstruct the creating command for a table Deparse a table into a set of SQL commands that can be used to reconstruct it. Together with column definiton it deparses constraints, indexes, triggers and rules as well. There are some table types that are not supported: temporary, partitioned, foreign, inherited and a table that uses options. Row security is also not supported. --- src/CMakeLists.txt | 1 + src/chunk_constraint.c | 55 ++-- src/compat.h | 2 + src/constraint.c | 67 ++++ src/constraint.h | 32 ++ test/pg_regress.sh | 5 +- test/runner.sh | 1 + test/sql/utils/testsupport.sql | 7 + tsl/src/CMakeLists.txt | 1 + tsl/src/deparse.c | 373 ++++++++++++++++++++++ tsl/src/deparse.h | 36 +++ tsl/test/expected/deparse.out | 11 + tsl/test/expected/deparse_fail.out | 24 ++ tsl/test/sql/CMakeLists.txt | 2 + tsl/test/sql/deparse.sql | 30 ++ tsl/test/sql/deparse_fail.sql | 21 ++ tsl/test/sql/include/deparse_create.sql | 51 +++ tsl/test/sql/include/deparse_func.sql | 10 + tsl/test/sql/include/deparse_recreate.sql | 24 ++ tsl/test/src/CMakeLists.txt | 1 + tsl/test/src/deparse.c | 19 ++ 21 files changed, 747 insertions(+), 26 deletions(-) create mode 100644 src/constraint.c create mode 100644 src/constraint.h create mode 100644 tsl/src/deparse.c create mode 100644 tsl/src/deparse.h create mode 100644 tsl/test/expected/deparse.out create mode 100644 tsl/test/expected/deparse_fail.out create mode 100644 tsl/test/sql/deparse.sql create mode 100644 tsl/test/sql/deparse_fail.sql create mode 100644 tsl/test/sql/include/deparse_create.sql create mode 100644 tsl/test/sql/include/deparse_func.sql create mode 100644 tsl/test/sql/include/deparse_recreate.sql create mode 100644 tsl/test/src/deparse.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb5c52ae1..fa65314a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES chunk_index.c chunk_insert_state.c chunk_server.c + constraint.c constraint_aware_append.c cross_module_fn.c copy.c diff --git a/src/chunk_constraint.c b/src/chunk_constraint.c index 12c4a001d..06583f184 100644 --- a/src/chunk_constraint.c +++ b/src/chunk_constraint.c @@ -31,6 +31,7 @@ #include "scan_iterator.h" #include "chunk_constraint.h" #include "chunk_index.h" +#include "constraint.h" #include "dimension_vector.h" #include "dimension_slice.h" #include "hypercube.h" @@ -579,36 +580,40 @@ ts_chunk_constraints_add_dimension_constraints(ChunkConstraints *ccs, int32 chun return cube->num_slices; } +typedef struct ConstraintContext +{ + int num_added; + char chunk_relkind; + ChunkConstraints *ccs; + int32 chunk_id; +} ConstraintContext; + +static ConstraintProcessStatus +chunk_constraint_add(HeapTuple constraint_tuple, void *arg) +{ + ConstraintContext *cc = arg; + Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraint_tuple); + + if (chunk_constraint_need_on_chunk(cc->chunk_relkind, constraint)) + { + chunk_constraints_add(cc->ccs, cc->chunk_id, 0, NULL, NameStr(constraint->conname)); + return CONSTR_PROCESSED; + } + + return CONSTR_IGNORED; +} + int ts_chunk_constraints_add_inheritable_constraints(ChunkConstraints *ccs, int32 chunk_id, const char chunk_relkind, Oid hypertable_oid) { - ScanKeyData skey; - Relation rel; - SysScanDesc scan; - HeapTuple htup; - int num_added = 0; + ConstraintContext cc = { + .chunk_relkind = chunk_relkind, + .ccs = ccs, + .chunk_id = chunk_id, + }; - ScanKeyInit(&skey, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, hypertable_oid); - - rel = table_open(ConstraintRelationId, AccessShareLock); - scan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true, NULL, 1, &skey); - - while (HeapTupleIsValid(htup = systable_getnext(scan))) - { - Form_pg_constraint pg_constraint = (Form_pg_constraint) GETSTRUCT(htup); - - if (chunk_constraint_need_on_chunk(chunk_relkind, pg_constraint)) - { - chunk_constraints_add(ccs, chunk_id, 0, NULL, NameStr(pg_constraint->conname)); - num_added++; - } - } - - systable_endscan(scan); - table_close(rel, AccessShareLock); - - return num_added; + return ts_constraint_process(hypertable_oid, chunk_constraint_add, &cc); } void diff --git a/src/compat.h b/src/compat.h index 3260c1b24..b07cd2ca6 100644 --- a/src/compat.h +++ b/src/compat.h @@ -400,6 +400,8 @@ MakeTupleTableSlotCompat(TupleDesc tupdesc, void *tts_ops) } name##data; \ FunctionCallInfo name = &name##data.fcinfo +#define SizeForFunctionCallInfo(nargs) sizeof(FunctionCallInfoData) + /* convenience macro to allocate FunctionCallInfoData on the heap */ #define HEAP_FCINFO(nargs) palloc(sizeof(FunctionCallInfoData)) diff --git a/src/constraint.c b/src/constraint.c new file mode 100644 index 000000000..308345152 --- /dev/null +++ b/src/constraint.c @@ -0,0 +1,67 @@ +/* + * This file and its contents are licensed under the Apache License 2.0. + * Please see the included NOTICE for copyright information and + * LICENSE-APACHE for a copy of the license. + */ +#include +#include +#include +#include +#include + +#include "compat.h" +#if PG11_LT /* PG11 consolidates pg_foo_fn.h -> pg_foo.h */ +#include +#endif +#if PG12_GE +#include +#else +#include "compat/tableam.h" +#endif + +#include "constraint.h" + +/* + * Process constraints that belong to a given relation. + * + * Returns the number of constraints processed. + */ +int +ts_constraint_process(Oid relid, constraint_func process_func, void *ctx) +{ + ScanKeyData skey; + Relation rel; + SysScanDesc scan; + HeapTuple htup; + bool should_continue = true; + int count = 0; + + ScanKeyInit(&skey, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, relid); + + rel = table_open(ConstraintRelationId, AccessShareLock); + scan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true, NULL, 1, &skey); + + while (HeapTupleIsValid(htup = systable_getnext(scan)) && should_continue) + { + switch (process_func(htup, ctx)) + { + case CONSTR_PROCESSED: + count++; + break; + case CONSTR_PROCESSED_DONE: + count++; + should_continue = false; + break; + case CONSTR_IGNORED: + break; + case CONSTR_IGNORED_DONE: + should_continue = false; + break; + } + } + + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return count; +} diff --git a/src/constraint.h b/src/constraint.h new file mode 100644 index 000000000..2049d8a8a --- /dev/null +++ b/src/constraint.h @@ -0,0 +1,32 @@ +/* + * This file and its contents are licensed under the Apache License 2.0. + * Please see the included NOTICE for copyright information and + * LICENSE-APACHE for a copy of the license. + */ +#ifndef TIMESCALEDB_CONSTRAINT_H +#define TIMESCALEDB_CONSTRAINT_H + +#include +#include + +#include "export.h" + +/* + * Return status for constraint processsing function. + * + * PROCESSED - count the constraint as processed + * IGNORED - the constraint wasn't processed + * DONE - stop processing constraints + */ +typedef enum ConstraintProcessStatus +{ + CONSTR_PROCESSED, + CONSTR_PROCESSED_DONE, + CONSTR_IGNORED, + CONSTR_IGNORED_DONE, +} ConstraintProcessStatus; + +typedef ConstraintProcessStatus (*constraint_func)(HeapTuple constraint_tuple, void *ctx); +extern TSDLLEXPORT int ts_constraint_process(Oid relid, constraint_func process_func, void *ctx); + +#endif /* TIMESCALEDB_CONSTRAINT_H */ diff --git a/test/pg_regress.sh b/test/pg_regress.sh index e39f4598a..856f1c8f0 100755 --- a/test/pg_regress.sh +++ b/test/pg_regress.sh @@ -87,6 +87,8 @@ function cleanup() { rm -rf ${TEST_TABLESPACE2_PATH} rm -f ${CURRENT_DIR}/.pg_init rm -f ${TEMP_SCHEDULE} + rm -rf ${TEST_TABLESPACE3_PATH} + rm -f ${TEST_OUTPUT_DIR}/.pg_init } trap cleanup EXIT @@ -94,7 +96,8 @@ trap cleanup EXIT # This mktemp line will work on both OSX and GNU systems TEST_TABLESPACE1_PATH=${TEST_TABLESPACE1_PATH:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')} TEST_TABLESPACE2_PATH=${TEST_TABLESPACE2_PATH:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')} -export TEST_TABLESPACE1_PATH TEST_TABLESPACE2_PATH +TEST_TABLESPACE3_PATH=${TEST_TABLESPACE3_PATH:-$(mktemp -d 2>/dev/null || mktemp -d -t 'timescaledb_regress')} +export TEST_TABLESPACE1_PATH TEST_TABLESPACE2_PATH TEST_TABLESPACE3_PATH rm -f ${TEST_OUTPUT_DIR}/.pg_init mkdir -p ${EXE_DIR}/sql/dump diff --git a/test/runner.sh b/test/runner.sh index 92d4c507d..f955c1fd4 100755 --- a/test/runner.sh +++ b/test/runner.sh @@ -69,6 +69,7 @@ ${PSQL} -U ${TEST_PGUSER} \ -v TEST_DBNAME="${TEST_DBNAME}" \ -v TEST_TABLESPACE1_PATH=\'${TEST_TABLESPACE1_PATH}\' \ -v TEST_TABLESPACE2_PATH=\'${TEST_TABLESPACE2_PATH}\' \ + -v TEST_TABLESPACE3_PATH=\'${TEST_TABLESPACE3_PATH}\' \ -v TEST_INPUT_DIR=${TEST_INPUT_DIR} \ -v TEST_OUTPUT_DIR=${TEST_OUTPUT_DIR} \ -v TEST_SPINWAIT_ITERS=${TEST_SPINWAIT_ITERS} \ diff --git a/test/sql/utils/testsupport.sql b/test/sql/utils/testsupport.sql index 1eb6a54b8..c2fff1b7e 100644 --- a/test/sql/utils/testsupport.sql +++ b/test/sql/utils/testsupport.sql @@ -272,3 +272,10 @@ $BODY$; -- Used to set a deterministic memory setting during tests CREATE OR REPLACE FUNCTION test.set_memory_cache_size(memory_amount text) RETURNS BIGINT AS :MODULE_PATHNAME, 'ts_set_memory_cache_size' LANGUAGE C VOLATILE STRICT; + +CREATE OR REPLACE FUNCTION test.empty_trigger_func() + RETURNS TRIGGER LANGUAGE PLPGSQL AS +$BODY$ +BEGIN +END +$BODY$; \ No newline at end of file diff --git a/tsl/src/CMakeLists.txt b/tsl/src/CMakeLists.txt index 08718bbb7..09e0df423 100644 --- a/tsl/src/CMakeLists.txt +++ b/tsl/src/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES chunk_api.c + deparse.c hypertable.c init.c license.c diff --git a/tsl/src/deparse.c b/tsl/src/deparse.c new file mode 100644 index 000000000..c090e442b --- /dev/null +++ b/tsl/src/deparse.c @@ -0,0 +1,373 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "export.h" +#include "compat.h" +#include "constraint.h" +#include "trigger.h" +#include "utils.h" +#include "deparse.h" + +/* + * Deparse a table into a set of SQL commands that can be used to recreate it. + * Together with column definiton it deparses constraints, indexes, triggers + * and rules as well. There are some table types that are not supported: + * temporary, partitioned, foreign, inherited and a table that uses + * options. Row security is also not supported. + */ +typedef const char *(*GetCmdFunc)(Oid oid); + +static const char * +get_index_cmd(Oid oid) +{ + return pg_get_indexdef_string(oid); +} + +static const char * +get_constraint_cmd(Oid oid) +{ + return pg_get_constraintdef_command(oid); +} + +static FunctionCallInfo +build_fcinfo_data(Oid oid) +{ + FunctionCallInfo fcinfo = palloc(SizeForFunctionCallInfo(1)); + + InitFunctionCallInfoData(*fcinfo, NULL, 1, InvalidOid, NULL, NULL); + FC_ARG(fcinfo, 0) = ObjectIdGetDatum(oid); + FC_NULL(fcinfo, 0) = false; + + return fcinfo; +} + +static const char * +get_trigger_cmd(Oid oid) +{ + return TextDatumGetCString(pg_get_triggerdef(build_fcinfo_data(oid))); +} + +static const char * +get_rule_cmd(Oid oid) +{ + return TextDatumGetCString(pg_get_ruledef(build_fcinfo_data(oid))); +} + +static List * +get_cmds(List *oids, GetCmdFunc get_cmd) +{ + List *cmds = NIL; + ListCell *cell; + + foreach (cell, oids) + { + StringInfo cmd = makeStringInfo(); + + appendStringInfo(cmd, "%s;", get_cmd(lfirst_oid(cell))); + cmds = lappend(cmds, cmd->data); + } + return cmds; +} + +static List * +get_constraint_cmds(List *constraint_oids) +{ + return get_cmds(constraint_oids, get_constraint_cmd); +} + +static List * +get_index_cmds(List *index_oids) +{ + return get_cmds(index_oids, get_index_cmd); +} + +static List * +get_trigger_cmds(List *trigger_oids) +{ + return get_cmds(trigger_oids, get_trigger_cmd); +} + +static List * +get_rule_cmds(List *rule_oids) +{ + return get_cmds(rule_oids, get_rule_cmd); +} + +static void +deparse_columns(StringInfo stmt, Relation rel) +{ + int att_idx; + TupleDesc rel_desc = RelationGetDescr(rel); + TupleConstr *constraints = rel_desc->constr; + + for (att_idx = 0; att_idx < rel_desc->natts; att_idx++) + { + int dim_idx; + Form_pg_attribute attr = TupleDescAttr(rel_desc, att_idx); + + if (attr->attisdropped) + continue; + + appendStringInfo(stmt, + "\"%s\" %s", + NameStr(attr->attname), + format_type_with_typemod(attr->atttypid, attr->atttypmod)); + + if (attr->attnotnull) + appendStringInfoString(stmt, " NOT NULL"); + + if (OidIsValid(attr->attcollation)) + appendStringInfo(stmt, " COLLATE \"%s\"", get_collation_name(attr->attcollation)); + + if (attr->atthasdef) + { + int co_idx; + + for (co_idx = 0; co_idx < constraints->num_defval; co_idx++) + { + AttrDefault attr_def = constraints->defval[co_idx]; + + if (attr->attnum == attr_def.adnum) + { + char *attr_default = + TextDatumGetCString(DirectFunctionCall2(pg_get_expr, + CStringGetTextDatum(attr_def.adbin), + ObjectIdGetDatum(rel->rd_id))); + + appendStringInfo(stmt, " DEFAULT %s", attr_default); + break; + } + } + } + + for (dim_idx = 1; dim_idx < attr->attndims; dim_idx++) + appendStringInfoString(stmt, "[]"); + + if (att_idx != (rel_desc->natts - 1)) + appendStringInfoString(stmt, ", "); + } +} + +typedef struct ConstraintContext +{ + List *constraints; + List **constraint_indexes; +} ConstraintContext; + +static ConstraintProcessStatus +add_constraint(HeapTuple constraint_tuple, void *ctx) +{ + ConstraintContext *cc = ctx; + Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraint_tuple); + Oid constroid; + + if (OidIsValid(constraint->conindid)) + *cc->constraint_indexes = lappend_oid(*cc->constraint_indexes, constraint->conindid); +#if PG12_GE + constroid = constraint->oid; +#else + constroid = HeapTupleGetOid(constraint_tuple); +#endif + cc->constraints = lappend_oid(cc->constraints, constroid); + return CONSTR_PROCESSED; +} + +static List * +get_constraint_oids(Oid relid, List **constraint_indexes) +{ + ConstraintContext cc = { + .constraints = NIL, + .constraint_indexes = constraint_indexes, + }; + + ts_constraint_process(relid, add_constraint, &cc); + + return cc.constraints; +} + +static List * +get_index_oids(Relation rel, List *exclude_indexes) +{ + List *indexes = NIL; + ListCell *cell; + + foreach (cell, RelationGetIndexList(rel)) + { + Oid indexid = lfirst_oid(cell); + + if (!list_member_oid(exclude_indexes, indexid)) + indexes = lappend_oid(indexes, indexid); + } + return indexes; +} + +static List * +get_trigger_oids(Relation rel) +{ + List *triggers = NIL; + + if (rel->trigdesc != NULL) + { + int i; + + for (i = 0; i < rel->trigdesc->numtriggers; i++) + { + const Trigger trigger = rel->trigdesc->triggers[i]; + + if (!trigger.tgisinternal) + triggers = lappend_oid(triggers, trigger.tgoid); + } + } + return triggers; +} + +static List * +get_rule_oids(Relation rel) +{ + List *rules = NIL; + + if (rel->rd_rules != NULL) + { + int i; + + for (i = 0; i < rel->rd_rules->numLocks; i++) + { + const RewriteRule *rule = rel->rd_rules->rules[i]; + + rules = lappend_oid(rules, rule->ruleId); + } + } + return rules; +} + +static void +validate_relation(Relation rel) +{ + if (rel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("given relation is not an ordinary table"))); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("temporary table is not supported"))); + + if (rel->rd_rel->relrowsecurity) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row security is not supported"))); +} + +TableInfo * +deparse_create_table_info(Oid relid) +{ + List *exclude_indexes = NIL; + TableInfo *table_info = palloc0(sizeof(TableInfo)); + Relation rel = table_open(relid, AccessShareLock); + + if (rel == NULL) + ereport(ERROR, (errmsg("relation with id %d not found", relid))); + + validate_relation(rel); + + table_info->relid = relid; + table_info->constraints = get_constraint_oids(relid, &exclude_indexes); + table_info->indexes = get_index_oids(rel, exclude_indexes); + table_info->triggers = get_trigger_oids(rel); + table_info->rules = get_rule_oids(rel); + table_close(rel, AccessShareLock); + return table_info; +} + +TableDef * +deparse_get_tabledef(TableInfo *table_info) +{ + StringInfo create_table = makeStringInfo(); + TableDef *table_def = palloc0(sizeof(TableDef)); + Relation rel = table_open(table_info->relid, AccessShareLock); + Oid tablespace; + + appendStringInfoString(create_table, "CREATE"); + if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) + appendStringInfoString(create_table, " UNLOGGED"); + appendStringInfoString(create_table, " TABLE"); + + appendStringInfo(create_table, + " \"%s\".\"%s\" (", + get_namespace_name(rel->rd_rel->relnamespace), + NameStr(rel->rd_rel->relname)); + + deparse_columns(create_table, rel); + + appendStringInfoChar(create_table, ')'); + + tablespace = get_rel_tablespace(table_info->relid); + if (tablespace != InvalidOid) + appendStringInfo(create_table, " TABLESPACE %s", get_tablespace_name(tablespace)); + + appendStringInfoChar(create_table, ';'); + table_def->create_cmd = create_table->data; + + table_def->constraint_cmds = get_constraint_cmds(table_info->constraints); + table_def->index_cmds = get_index_cmds(table_info->indexes); + table_def->trigger_cmds = get_trigger_cmds(table_info->triggers); + table_def->rule_cmds = get_rule_cmds(table_info->rules); + + table_close(rel, AccessShareLock); + return table_def; +} + +List * +deparse_get_tabledef_commands(Oid relid) +{ + TableInfo *table_info = deparse_create_table_info(relid); + TableDef *table_def = deparse_get_tabledef(table_info); + + return deparse_get_tabledef_commands_from_tabledef(table_def); +} + +List * +deparse_get_tabledef_commands_from_tabledef(TableDef *table_def) +{ + List *cmds = NIL; + + cmds = lappend(cmds, (char *) table_def->create_cmd); + cmds = list_concat(cmds, table_def->constraint_cmds); + cmds = list_concat(cmds, table_def->index_cmds); + cmds = list_concat(cmds, table_def->trigger_cmds); + cmds = list_concat(cmds, table_def->rule_cmds); + return cmds; +} + +const char * +deparse_get_tabledef_commands_concat(Oid relid) +{ + StringInfo tabledef = makeStringInfo(); + ListCell *cell; + + foreach (cell, deparse_get_tabledef_commands(relid)) + appendStringInfoString(tabledef, lfirst(cell)); + + return tabledef->data; +} diff --git a/tsl/src/deparse.h b/tsl/src/deparse.h new file mode 100644 index 000000000..be112bd42 --- /dev/null +++ b/tsl/src/deparse.h @@ -0,0 +1,36 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ +#ifndef TIMESCALEDB_DEPARSE_H +#define TIMESCALEDB_DEPARSE_H + +#include +#include + +typedef struct TableInfo +{ + Oid relid; + List *constraints; + List *indexes; + List *triggers; + List *rules; +} TableInfo; + +typedef struct TableDef +{ + const char *create_cmd; + List *constraint_cmds; + List *index_cmds; + List *trigger_cmds; + List *rule_cmds; +} TableDef; + +TableInfo *deparse_create_table_info(Oid relid); +TableDef *deparse_get_tabledef(TableInfo *table_info); +List *deparse_get_tabledef_commands(Oid relid); +List *deparse_get_tabledef_commands_from_tabledef(TableDef *table_def); +const char *deparse_get_tabledef_commands_concat(Oid relid); + +#endif diff --git a/tsl/test/expected/deparse.out b/tsl/test/expected/deparse.out new file mode 100644 index 000000000..b91073def --- /dev/null +++ b/tsl/test/expected/deparse.out @@ -0,0 +1,11 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. +-- We compare information(\d+) about manually created tables with the ones that were recreated using deparse_table command. +-- There should be no diff. +\set ECHO errors + ?column? +---------- + DONE +(1 row) + diff --git a/tsl/test/expected/deparse_fail.out b/tsl/test/expected/deparse_fail.out new file mode 100644 index 000000000..cd9ddb91d --- /dev/null +++ b/tsl/test/expected/deparse_fail.out @@ -0,0 +1,24 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. +\ir include/deparse_func.sql +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. +\c :TEST_DBNAME :ROLE_SUPERUSER +CREATE OR REPLACE FUNCTION _timescaledb_internal.get_tabledef(tbl REGCLASS) RETURNS TEXT +AS :TSL_MODULE_PATHNAME, 'tsl_test_get_tabledef' LANGUAGE C VOLATILE STRICT; +SET ROLE :ROLE_DEFAULT_PERM_USER; +\set ON_ERROR_STOP 0 +CREATE TEMP TABLE fail_table1(x INT); +SELECT _timescaledb_internal.get_tabledef('fail_table1'); +ERROR: temporary table is not supported +CREATE INDEX my_fail_table1_idx ON fail_table1 USING BTREE(x); +SELECT _timescaledb_internal.get_tabledef('my_fail_table1_idx'); +ERROR: "my_fail_table1_idx" is an index +SELECT _timescaledb_internal.get_tabledef('non_existing'); +ERROR: relation "non_existing" does not exist at character 43 +CREATE TABLE row_sec(i INT); +ALTER TABLE row_sec ENABLE ROW LEVEL SECURITY; +SELECT _timescaledb_internal.get_tabledef('row_sec'); +ERROR: row security is not supported diff --git a/tsl/test/sql/CMakeLists.txt b/tsl/test/sql/CMakeLists.txt index 861c9e114..ab45cb760 100644 --- a/tsl/test/sql/CMakeLists.txt +++ b/tsl/test/sql/CMakeLists.txt @@ -100,6 +100,8 @@ if (NOT ${PG_VERSION} VERSION_LESS "10") timescaledb_fdw.sql ) list(APPEND TEST_FILES_DEBUG + deparse.sql + deparse_fail.sql dist_commands.sql remote_connection.sql remote_connection_cache.sql diff --git a/tsl/test/sql/deparse.sql b/tsl/test/sql/deparse.sql new file mode 100644 index 000000000..05ae07329 --- /dev/null +++ b/tsl/test/sql/deparse.sql @@ -0,0 +1,30 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +-- We compare information(\d+) about manually created tables with the ones that were recreated using deparse_table command. +-- There should be no diff. + +\set ECHO errors +\ir include/deparse_func.sql + +SELECT format('%s/results/deparse_create.out', :'TEST_OUTPUT_DIR') AS "CREATE_OUT", + format('%s/results/deparse_recreate.out', :'TEST_OUTPUT_DIR') AS "RECREATE_OUT" +\gset +SELECT format('\! diff %s %s', :'CREATE_OUT', :'RECREATE_OUT') as "DIFF_CMD" +\gset + +\ir include/deparse_create.sql + +\o :CREATE_OUT +\d+ "public".* + +\ir include/deparse_recreate.sql +\o :RECREATE_OUT +\d+ "public".* + +\o + +:DIFF_CMD + +SELECT 'DONE' diff --git a/tsl/test/sql/deparse_fail.sql b/tsl/test/sql/deparse_fail.sql new file mode 100644 index 000000000..fa8bf6ae8 --- /dev/null +++ b/tsl/test/sql/deparse_fail.sql @@ -0,0 +1,21 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +\ir include/deparse_func.sql + +\set ON_ERROR_STOP 0 + +CREATE TEMP TABLE fail_table1(x INT); + +SELECT _timescaledb_internal.get_tabledef('fail_table1'); + +CREATE INDEX my_fail_table1_idx ON fail_table1 USING BTREE(x); + +SELECT _timescaledb_internal.get_tabledef('my_fail_table1_idx'); + +SELECT _timescaledb_internal.get_tabledef('non_existing'); + +CREATE TABLE row_sec(i INT); +ALTER TABLE row_sec ENABLE ROW LEVEL SECURITY; +SELECT _timescaledb_internal.get_tabledef('row_sec'); diff --git a/tsl/test/sql/include/deparse_create.sql b/tsl/test/sql/include/deparse_create.sql new file mode 100644 index 000000000..9825b0459 --- /dev/null +++ b/tsl/test/sql/include/deparse_create.sql @@ -0,0 +1,51 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +-- Lets create some tabels that we will try to deparse and recreate + +\c :TEST_DBNAME :ROLE_SUPERUSER + +SET ROLE :ROLE_DEFAULT_PERM_USER; + +CREATE TABLE table1(time TIMESTAMP, v FLOAT8, c CHAR(10), x NUMERIC(10,4), i interval hour to minute); + +CREATE TABLE table2(time TIMESTAMP NOT NULL, v FLOAT8[], d TEXT COLLATE "POSIX", num INT DEFAULT 100); + +CREATE TABLE table3(time TIMESTAMP PRIMARY KEY, v FLOAT8[][], num INT CHECK (num > 0), d INT UNIQUE, CONSTRAINT validate_num_and_d CHECK ( num > d)); + +CREATE TABLE table4(t TIMESTAMP , d INT, PRIMARY KEY (t, d)); + +CREATE TABLE ref_table(id INT PRIMARY KEY, d TEXT); + +CREATE TABLE table5(t TIMESTAMP PRIMARY KEY, v FLOAT8, d INT REFERENCES ref_table ON DELETE CASCADE); + +CREATE SEQUENCE my_seq; + +CREATE UNLOGGED TABLE table6(id INT NOT NULL DEFAULT nextval('my_seq'), t TEXT); + +CREATE INDEX ON table6 USING BTREE (t); + +RESET ROLE; + +CREATE TABLESPACE mytablespace OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE3_PATH; + +CREATE TYPE device_status AS ENUM ('OFF', 'ON', 'BROKEN'); + +CREATE SCHEMA myschema AUTHORIZATION :ROLE_DEFAULT_PERM_USER;; + +SET ROLE :ROLE_DEFAULT_PERM_USER; + +CREATE TABLE table7(t TIMESTAMP, v INT) TABLESPACE mytablespace; + +CREATE TABLE table8(id INT, status device_status); + +CREATE TRIGGER test_trigger BEFORE UPDATE OR DELETE ON table8 +FOR EACH STATEMENT EXECUTE PROCEDURE test.empty_trigger_func(); + +CREATE RULE notify_me AS ON UPDATE TO table8 DO ALSO NOTIFY table8; + +CREATE TABLE table9(c CIRCLE, EXCLUDE USING gist (c WITH &&)); + +CREATE TABLE myschema.table10(t TIMESTAMP); + diff --git a/tsl/test/sql/include/deparse_func.sql b/tsl/test/sql/include/deparse_func.sql new file mode 100644 index 000000000..b1979daa4 --- /dev/null +++ b/tsl/test/sql/include/deparse_func.sql @@ -0,0 +1,10 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +\c :TEST_DBNAME :ROLE_SUPERUSER + +CREATE OR REPLACE FUNCTION _timescaledb_internal.get_tabledef(tbl REGCLASS) RETURNS TEXT +AS :TSL_MODULE_PATHNAME, 'tsl_test_get_tabledef' LANGUAGE C VOLATILE STRICT; + +SET ROLE :ROLE_DEFAULT_PERM_USER; \ No newline at end of file diff --git a/tsl/test/sql/include/deparse_recreate.sql b/tsl/test/sql/include/deparse_recreate.sql new file mode 100644 index 000000000..e1d9e6192 --- /dev/null +++ b/tsl/test/sql/include/deparse_recreate.sql @@ -0,0 +1,24 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +-- deparse each table%, drop it and recreate it using deparse result + +DO +$$DECLARE + tables CURSOR FOR + SELECT tablename, schemaname + FROM pg_tables + WHERE tablename LIKE 'table%' AND (schemaname = 'public' OR schemaname = 'myschema') + ORDER BY tablename; + deparse_stmt text; + tablename text; +BEGIN + FOR table_record IN tables + LOOP + tablename := format('%I.%I', table_record.schemaname, table_record.tablename); + EXECUTE format('SELECT _timescaledb_internal.get_tabledef(%L)', tablename) INTO deparse_stmt; + EXECUTE 'DROP TABLE ' || tablename; + EXECUTE deparse_stmt; + END LOOP; +END$$; diff --git a/tsl/test/src/CMakeLists.txt b/tsl/test/src/CMakeLists.txt index 8e86d70f1..41c87c0e2 100644 --- a/tsl/test/src/CMakeLists.txt +++ b/tsl/test/src/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES test_ddl_hook.c test_continuous_aggs.c server.c + deparse.c ) include(${PROJECT_SOURCE_DIR}/tsl/src/build-defs.cmake) diff --git a/tsl/test/src/deparse.c b/tsl/test/src/deparse.c new file mode 100644 index 000000000..00bba755c --- /dev/null +++ b/tsl/test/src/deparse.c @@ -0,0 +1,19 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ +#include +#include +#include "export.h" +#include "deparse.h" + +TS_FUNCTION_INFO_V1(tsl_test_get_tabledef); + +Datum +tsl_test_get_tabledef(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + const char *cmd = deparse_get_tabledef_commands_concat(relid); + PG_RETURN_TEXT_P(cstring_to_text(cmd)); +}