1
0
mirror of https://github.com/timescale/timescaledb.git synced 2025-05-19 20:24:46 +08:00

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.
This commit is contained in:
niksa 2019-01-28 12:36:44 +01:00 committed by Erik Nordström
parent e2371558f7
commit 4368fcff3c
21 changed files with 747 additions and 26 deletions

@ -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

@ -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

@ -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))

67
src/constraint.c Normal file

@ -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 <postgres.h>
#include <access/genam.h>
#include <catalog/indexing.h>
#include <catalog/pg_constraint.h>
#include <utils/fmgroids.h>
#include "compat.h"
#if PG11_LT /* PG11 consolidates pg_foo_fn.h -> pg_foo.h */
#include <catalog/pg_constraint_fn.h>
#endif
#if PG12_GE
#include <access/table.h>
#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;
}

32
src/constraint.h Normal file

@ -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 <postgres.h>
#include <access/htup.h>
#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 */

@ -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

@ -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} \

@ -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$;

@ -1,5 +1,6 @@
set(SOURCES
chunk_api.c
deparse.c
hypertable.c
init.c
license.c

373
tsl/src/deparse.c Normal file

@ -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 <postgres.h>
#include <utils/rel.h>
#include <lib/stringinfo.h>
#include <utils/builtins.h>
#include <utils/lsyscache.h>
#include <utils/relcache.h>
#include <catalog/indexing.h>
#include <utils/ruleutils.h>
#include <utils/syscache.h>
#include <commands/tablespace.h>
#include <catalog/pg_class.h>
#include <utils/rel.h>
#include <access/relscan.h>
#include <utils/fmgroids.h>
#include <catalog/pg_constraint.h>
#include <catalog/pg_index.h>
#include <nodes/pg_list.h>
#include <fmgr.h>
#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;
}

36
tsl/src/deparse.h Normal file

@ -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 <postgres.h>
#include <nodes/pg_list.h>
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

@ -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)

@ -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

@ -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

30
tsl/test/sql/deparse.sql Normal file

@ -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'

@ -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');

@ -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);

@ -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;

@ -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$$;

@ -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)

19
tsl/test/src/deparse.c Normal file

@ -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 <postgres.h>
#include <utils/builtins.h>
#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));
}