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:
parent
e2371558f7
commit
4368fcff3c
src
test
tsl
@ -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
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
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
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
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
|
11
tsl/test/expected/deparse.out
Normal file
11
tsl/test/expected/deparse.out
Normal file
@ -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)
|
||||
|
24
tsl/test/expected/deparse_fail.out
Normal file
24
tsl/test/expected/deparse_fail.out
Normal file
@ -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
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'
|
21
tsl/test/sql/deparse_fail.sql
Normal file
21
tsl/test/sql/deparse_fail.sql
Normal file
@ -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');
|
51
tsl/test/sql/include/deparse_create.sql
Normal file
51
tsl/test/sql/include/deparse_create.sql
Normal file
@ -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);
|
||||
|
10
tsl/test/sql/include/deparse_func.sql
Normal file
10
tsl/test/sql/include/deparse_func.sql
Normal file
@ -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;
|
24
tsl/test/sql/include/deparse_recreate.sql
Normal file
24
tsl/test/sql/include/deparse_recreate.sql
Normal file
@ -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
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));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user