5875 lines
226 KiB
C++
5875 lines
226 KiB
C++
/*-------------------------------------------------------------------------
|
|
*
|
|
* ddldeparse.cpp
|
|
* Functions to convert utility commands to machine-parseable
|
|
* representation
|
|
*
|
|
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* NOTES
|
|
*
|
|
* This is intended to provide JSON blobs representing DDL commands, which can
|
|
* later be re-processed into plain strings by well-defined sprintf-like
|
|
* expansion. These JSON objects are intended to allow for machine-editing of
|
|
* the commands, by replacing certain nodes within the objects.
|
|
*
|
|
* Much of the information in the output blob actually comes from system
|
|
* catalogs, not from the command parse node, as it is impossible to reliably
|
|
* construct a fully-specified command (i.e. one not dependent on search_path
|
|
* etc) looking only at the parse node.
|
|
*
|
|
* Deparse object tree is created by using:
|
|
* a) new_objtree("know contents") where the complete tree content is known or
|
|
* the initial tree content is known.
|
|
* b) new_objtree("") for the syntax where the object tree will be derived
|
|
* based on some conditional checks.
|
|
* c) new_objtree_VA where the complete tree can be derived using some fixed
|
|
* content and/or some variable arguments.
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/ddl_deparse.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/amapi.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_am.h"
|
|
#include "catalog/pg_aggregate.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_cast.h"
|
|
#include "catalog/pg_collation.h"
|
|
#include "catalog/pg_constraint.h"
|
|
#include "catalog/pg_conversion.h"
|
|
#include "catalog/pg_depend.h"
|
|
#include "catalog/pg_extension.h"
|
|
#include "catalog/pg_foreign_data_wrapper.h"
|
|
#include "catalog/pg_foreign_server.h"
|
|
#include "catalog/pg_inherits.h"
|
|
#include "catalog/pg_language.h"
|
|
#include "catalog/pg_largeobject.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_opclass.h"
|
|
#include "catalog/pg_operator.h"
|
|
#include "catalog/pg_opfamily.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "catalog/pg_range.h"
|
|
#include "catalog/pg_rewrite.h"
|
|
#include "catalog/pg_statistic_ext.h"
|
|
#include "catalog/pg_trigger.h"
|
|
#include "catalog/pg_ts_config.h"
|
|
#include "catalog/pg_ts_dict.h"
|
|
#include "catalog/pg_ts_parser.h"
|
|
#include "catalog/pg_ts_template.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "catalog/pg_user_mapping.h"
|
|
#include "catalog/heap.h"
|
|
#include "commands/defrem.h"
|
|
#include "commands/sequence.h"
|
|
#include "commands/tablespace.h"
|
|
#include "foreign/foreign.h"
|
|
#include "funcapi.h"
|
|
#include "mb/pg_wchar.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/parsenodes.h"
|
|
#include "parser/parse_type.h"
|
|
#include "rewrite/rewriteHandler.h"
|
|
#include "tcop/ddldeparse.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/guc_tables.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/jsonb.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/partitionkey.h"
|
|
#include "utils/syscache.h"
|
|
#include "optimizer/clauses.h"
|
|
|
|
/* Estimated length of the generated jsonb string */
|
|
static const int JSONB_ESTIMATED_LEN = 128;
|
|
|
|
/* copy from ruleutils.cpp */
|
|
#define BEGIN_P_STR " BEGIN_B_PROC " /* used in dolphin type proc body */
|
|
#define BEGIN_P_LEN 14
|
|
#define BEGIN_N_STR " BEGIN " /* BEGIN_P_STR to same length */
|
|
|
|
/*
|
|
* Mark the max_volatility flag for an expression in the command.
|
|
*/
|
|
static void mark_function_volatile(ddl_deparse_context* context, Node* expr)
|
|
{
|
|
if (context->max_volatility == PROVOLATILE_VOLATILE) {
|
|
return;
|
|
}
|
|
|
|
if (contain_volatile_functions(expr)) {
|
|
context->max_volatility = PROVOLATILE_VOLATILE;
|
|
return;
|
|
}
|
|
|
|
if (context->max_volatility == PROVOLATILE_IMMUTABLE &&
|
|
contain_mutable_functions(expr)) {
|
|
context->max_volatility = PROVOLATILE_STABLE;
|
|
}
|
|
}
|
|
|
|
static void check_alter_table_rewrite_replident_change(Relation r, int attno, const char *cmd)
|
|
{
|
|
Oid replidindex = RelationGetReplicaIndex(r);
|
|
if (!OidIsValid(replidindex)) {
|
|
ereport(ERROR,
|
|
(errmsg("cannot use %s command without replident index because it cannot be replicated in DDL replication",
|
|
cmd)));
|
|
}
|
|
|
|
if (IsRelationReplidentKey(r, attno)) {
|
|
ereport(ERROR,
|
|
(errmsg("cannot use %s command to replica index attr because it cannot be replicated in DDL replication",
|
|
cmd)));
|
|
}
|
|
}
|
|
|
|
static void check_alter_table_replident(Relation rel)
|
|
{
|
|
if (rel->relreplident != REPLICA_IDENTITY_FULL &&
|
|
!OidIsValid(RelationGetReplicaIndex(rel))) {
|
|
elog(ERROR, "this ALTER TABLE command will cause a table rewritting, "
|
|
"but the table does not have a replica identity, it cannot be replicated in DDL replication");
|
|
}
|
|
}
|
|
|
|
void table_close(Relation relation, LOCKMODE lockmode)
|
|
{
|
|
relation_close(relation, lockmode);
|
|
}
|
|
|
|
/*
|
|
* Reduce some unnecessary strings from the output json when verbose
|
|
* and "present" member is false. This means these strings won't be merged into
|
|
* the last DDL command.
|
|
*/
|
|
|
|
static char* append_object_to_format_string(ObjTree *tree, const char *sub_fmt);
|
|
static void append_premade_object(ObjTree *tree, ObjElem *elem);
|
|
static void append_int_object(ObjTree *tree, char *sub_fmt, int32 value);
|
|
static void append_float_object(ObjTree *tree, char *sub_fmt, float8 value);
|
|
static void format_type_detailed(Oid type_oid, int32 typemod,
|
|
Oid *nspid, char **typname, char **typemodstr,
|
|
bool *typarray);
|
|
static ObjElem* new_object(ObjType type, char *name);
|
|
static ObjTree* new_objtree_for_qualname_id(Oid classId, Oid objectId);
|
|
static ObjTree* new_objtree(const char *fmt);
|
|
static ObjElem* new_object_object(ObjTree *value);
|
|
|
|
ObjTree* new_objtree_VA(const char *fmt, int numobjs, ...);
|
|
ObjElem* new_string_object(char *value);
|
|
|
|
static JsonbValue* objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner);
|
|
static void pg_get_indexdef_detailed(Oid indexrelid, bool global,
|
|
char **index_am,
|
|
char **definition,
|
|
char **reloptions,
|
|
char **tablespace,
|
|
char **whereClause,
|
|
bool *invisible);
|
|
static char* RelationGetColumnDefault(Relation rel, AttrNumber attno,
|
|
List *dpcontext, List **exprs);
|
|
|
|
static ObjTree* deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
|
|
ColumnDef *coldef, bool is_alter, List **exprs);
|
|
static ObjTree* deparse_ColumnSetOptions(AlterTableCmd *subcmd);
|
|
|
|
static ObjTree* deparse_DefElem(DefElem *elem, bool is_reset);
|
|
static ObjTree* deparse_OnCommitClause(OnCommitAction option);
|
|
static ObjTree* deparse_add_subpartition(ObjTree* ret, Oid partoid,
|
|
List *subPartitionDefState, int parkeynum, Oid* partkey_types);
|
|
static List* deparse_partition_boudaries(Oid parentoid, char reltype, char strategy, const char* partition_name,
|
|
Oid* partoid, int parkeynum, Oid* partkey_types);
|
|
static int get_partition_key_types(Oid reloid, char parttype, Oid **partkey_types);
|
|
static List* get_range_partition_maxvalues(List *boundary);
|
|
static List* get_list_partition_maxvalues(List *boundary);
|
|
static ObjTree* deparse_RelSetOptions(AlterTableCmd *subcmd);
|
|
|
|
static inline ObjElem* deparse_Seq_Cache(sequence_values *seqdata, bool alter_table);
|
|
static inline ObjElem* deparse_Seq_Cycle(sequence_values *seqdata, bool alter_table);
|
|
static inline ObjElem* deparse_Seq_IncrementBy(sequence_values *seqdata, bool alter_table);
|
|
static inline ObjElem* deparse_Seq_Minvalue(sequence_values *seqdata, bool alter_table);
|
|
static inline ObjElem* deparse_Seq_Maxvalue(sequence_values *seqdata, bool alter_table);
|
|
static inline ObjElem* deparse_Seq_Restart(char *last_value);
|
|
static inline ObjElem* deparse_Seq_Startwith(sequence_values *seqdata, bool alter_table);
|
|
static ObjElem* deparse_Seq_OwnedBy(Oid sequenceId);
|
|
static inline ObjElem* deparse_Seq_Order(DefElem *elem);
|
|
static inline ObjElem* deparse_Seq_As(DefElem *elem);
|
|
static ObjTree* deparse_CreateFunction(Oid objectId, Node *parsetree);
|
|
static ObjTree* deparse_FunctionSet(VariableSetKind kind, char *name, char *value);
|
|
static ObjTree* deparse_CreateTrigStmt(Oid objectId, Node *parsetree);
|
|
static ObjTree* deparse_AlterTrigStmt(Oid objectId, Node *parsetree);
|
|
static ObjTree* deparse_AlterFunction(Oid objectId, Node *parsetree);
|
|
|
|
static List* deparse_TableElements(Relation relation, List *tableElements, List *dpcontext, bool composite);
|
|
extern char* pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node* whenClause, bool pretty);
|
|
extern char* pg_get_functiondef_string(Oid funcid);
|
|
|
|
/*
|
|
* Append a boolean parameter to a tree.
|
|
*/
|
|
static void append_bool_object(ObjTree *tree, char *sub_fmt, bool value)
|
|
{
|
|
ObjElem *param;
|
|
char *object_name = sub_fmt;
|
|
bool is_present_flag = false;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
/*
|
|
* Check if the format string is 'present' and if yes, store the boolean
|
|
* value
|
|
*/
|
|
if (strcmp(sub_fmt, "present") == 0) {
|
|
is_present_flag = true;
|
|
tree->present = value;
|
|
}
|
|
|
|
if (!is_present_flag)
|
|
object_name = append_object_to_format_string(tree, sub_fmt);
|
|
|
|
param = new_object(ObjTypeBool, object_name);
|
|
param->value.boolean = value;
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
/*
|
|
* Append an int32 parameter to a tree.
|
|
*/
|
|
static void append_int_object(ObjTree *tree, char *sub_fmt, int32 value)
|
|
{
|
|
ObjElem *param;
|
|
char *object_name;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
object_name = append_object_to_format_string(tree, sub_fmt);
|
|
|
|
param = new_object(ObjTypeInteger, object_name);
|
|
param->value.integer = value;
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
/*
|
|
* Append a float8 parameter to a tree.
|
|
*/
|
|
static void append_float_object(ObjTree *tree, char *sub_fmt, float8 value)
|
|
{
|
|
ObjElem *param;
|
|
char *object_name;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
object_name = append_object_to_format_string(tree, sub_fmt);
|
|
|
|
param = new_object(ObjTypeFloat, object_name);
|
|
param->value.flt = value;
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
/*
|
|
* Append a NULL-or-quoted-literal clause. Userful for COMMENT and SECURITY
|
|
* LABEL.
|
|
*
|
|
* Verbose syntax
|
|
* %{null}s %{literal}s
|
|
*/
|
|
static void append_literal_or_null(ObjTree *parent, char *elemname, char *value)
|
|
{
|
|
ObjTree *top;
|
|
ObjTree *part;
|
|
|
|
top = new_objtree("");
|
|
part = new_objtree_VA("NULL", 1,
|
|
"present", ObjTypeBool, !value);
|
|
append_object_object(top, "%{null}s", part);
|
|
|
|
part = new_objtree_VA("", 1,
|
|
"present", ObjTypeBool, value != NULL);
|
|
|
|
if (value) {
|
|
append_string_object(part, "%{value}L", "value", value);
|
|
}
|
|
|
|
append_object_object(top, "%{literal}s", part);
|
|
|
|
append_object_object(parent, elemname, top);
|
|
}
|
|
|
|
/*
|
|
* Append an array parameter to a tree.
|
|
*/
|
|
void append_array_object(ObjTree *tree, char *sub_fmt, List *array)
|
|
{
|
|
ObjElem *param;
|
|
char *object_name;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
if (!array || list_length(array) == 0) {
|
|
return;
|
|
}
|
|
|
|
ListCell *lc;
|
|
|
|
/* Remove elements where present flag is false */
|
|
foreach(lc, array) {
|
|
ObjElem *elem = (ObjElem *) lfirst(lc);
|
|
|
|
if (elem->objtype != ObjTypeObject && elem->objtype != ObjTypeString) {
|
|
elog(ERROR, "ObjectTypeArray %s elem need to be object or string", sub_fmt);
|
|
}
|
|
}
|
|
|
|
object_name = append_object_to_format_string(tree, sub_fmt);
|
|
|
|
param = new_object(ObjTypeArray, object_name);
|
|
param->value.array = array;
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
|
|
/*
|
|
* Append the input format string to the ObjTree.
|
|
*/
|
|
void append_format_string(ObjTree *tree, char *sub_fmt)
|
|
{
|
|
int len;
|
|
char *fmt;
|
|
|
|
if (tree->fmtinfo == NULL) {
|
|
return;
|
|
}
|
|
|
|
fmt = tree->fmtinfo->data;
|
|
len = tree->fmtinfo->len;
|
|
|
|
/* Add a separator if necessary */
|
|
if (len > 0 && fmt[len - 1] != ' ') {
|
|
appendStringInfoSpaces(tree->fmtinfo, 1);
|
|
}
|
|
appendStringInfoString(tree->fmtinfo, sub_fmt);
|
|
}
|
|
|
|
/*
|
|
* Append present as false to a tree.
|
|
* If sub_fmt is passed and verbose mode is ON,
|
|
* append sub_fmt as well to tree.
|
|
*
|
|
* Example:
|
|
* in non-verbose mode, element will be like:
|
|
* "collation": {"fmt": "COLLATE", "present": false}
|
|
* in verbose mode:
|
|
* "collation": {"fmt": "COLLATE %{name}D", "present": false}
|
|
*/
|
|
static void append_not_present(ObjTree *tree, char *sub_fmt)
|
|
{
|
|
if (sub_fmt) {
|
|
append_format_string(tree, sub_fmt);
|
|
}
|
|
|
|
append_bool_object(tree, "present", false);
|
|
}
|
|
|
|
/*
|
|
* Append an object parameter to a tree.
|
|
*/
|
|
void append_object_object(ObjTree *tree, char *sub_fmt, ObjTree *value)
|
|
{
|
|
ObjElem *param;
|
|
char *object_name;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
if (!value)
|
|
return;
|
|
|
|
object_name = append_object_to_format_string(tree, sub_fmt);
|
|
|
|
param = new_object(ObjTypeObject, object_name);
|
|
param->value.object = value;
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
/*
|
|
* Return the object name which is extracted from the input "*%{name[:.]}*"
|
|
* style string. And append the input format string to the ObjTree.
|
|
*/
|
|
static char* append_object_to_format_string(ObjTree *tree, const char *sub_fmt)
|
|
{
|
|
StringInfoData object_name;
|
|
const char *end_ptr, *start_ptr;
|
|
int length;
|
|
char *tmp_str;
|
|
|
|
if (tree->fmtinfo == NULL)
|
|
elog(ERROR, "object fmtinfo not found");
|
|
|
|
initStringInfo(&object_name);
|
|
|
|
start_ptr = strchr(sub_fmt, '{');
|
|
end_ptr = strchr(sub_fmt, ':');
|
|
if (end_ptr == NULL)
|
|
end_ptr = strchr(sub_fmt, '}');
|
|
|
|
if (start_ptr != NULL && end_ptr != NULL) {
|
|
error_t rc = 0;
|
|
length = end_ptr - start_ptr - 1;
|
|
tmp_str = (char *) palloc(length + 1);
|
|
rc = strncpy_s(tmp_str, length + 1, start_ptr + 1, length);
|
|
securec_check_c(rc, "\0", "\0");
|
|
tmp_str[length] = '\0';
|
|
appendStringInfoString(&object_name, tmp_str);
|
|
pfree(tmp_str);
|
|
}
|
|
|
|
if (object_name.len == 0)
|
|
elog(ERROR, "object name not found");
|
|
|
|
append_format_string(tree, pstrdup(sub_fmt));
|
|
|
|
return object_name.data;
|
|
}
|
|
|
|
/*
|
|
* Append a preallocated parameter to a tree.
|
|
*/
|
|
static inline void append_premade_object(ObjTree *tree, ObjElem *elem)
|
|
{
|
|
slist_push_head(&tree->params, &elem->node);
|
|
tree->numParams++;
|
|
}
|
|
|
|
/*
|
|
* Append a string parameter to a tree.
|
|
*/
|
|
void append_string_object(ObjTree *tree, char *sub_fmt, char *name,
|
|
const char *value)
|
|
{
|
|
ObjElem *param;
|
|
|
|
Assert(sub_fmt);
|
|
|
|
if (value == NULL || value[0] == '\0')
|
|
return;
|
|
|
|
append_format_string(tree, sub_fmt);
|
|
param = new_object(ObjTypeString, name);
|
|
param->value.string = pstrdup(value);
|
|
append_premade_object(tree, param);
|
|
}
|
|
|
|
/*
|
|
* Similar to format_type_extended, except we return each bit of information
|
|
* separately:
|
|
*
|
|
* - nspid is the schema OID. For certain SQL-standard types which have weird
|
|
* typmod rules, we return InvalidOid; the caller is expected to not schema-
|
|
* qualify the name nor add quotes to the type name in this case.
|
|
*
|
|
* - typname is set to the type name, without quotes
|
|
*
|
|
* - typemodstr is set to the typemod, if any, as a string with parentheses
|
|
*
|
|
* - typarray indicates whether []s must be added
|
|
*
|
|
* We don't try to decode type names to their standard-mandated names, except
|
|
* in the cases of types with unusual typmod rules.
|
|
*/
|
|
static void format_type_detailed(Oid type_oid, int32 typemod,
|
|
Oid *nspid, char **typname, char **typemodstr,
|
|
bool *typearray)
|
|
{
|
|
HeapTuple tuple;
|
|
Form_pg_type typeform;
|
|
Oid array_base_type;
|
|
|
|
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
|
|
|
|
typeform = (Form_pg_type) GETSTRUCT(tuple);
|
|
|
|
/*
|
|
* Check if it's a regular (variable length) array type. As above,
|
|
* fixed-length array types such as "name" shouldn't get deconstructed.
|
|
*/
|
|
array_base_type = typeform->typelem;
|
|
|
|
*typearray = (typeform->typstorage != 'p') && (OidIsValid(array_base_type));
|
|
|
|
if (*typearray) {
|
|
/* Switch our attention to the array element type */
|
|
ReleaseSysCache(tuple);
|
|
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "cache lookup failed for type with OID %u", type_oid);
|
|
|
|
typeform = (Form_pg_type) GETSTRUCT(tuple);
|
|
type_oid = array_base_type;
|
|
}
|
|
|
|
/*
|
|
* Special-case crock for types with strange typmod rules where we put
|
|
* typemod at the middle of name (e.g. TIME(6) with time zone). We cannot
|
|
* schema-qualify nor add quotes to the type name in these cases.
|
|
*/
|
|
*nspid = InvalidOid;
|
|
|
|
switch (type_oid) {
|
|
case INTERVALOID:
|
|
*typname = pstrdup("INTERVAL");
|
|
break;
|
|
case TIMESTAMPTZOID:
|
|
if (typemod < 0)
|
|
*typname = pstrdup("TIMESTAMP WITH TIME ZONE");
|
|
else
|
|
/* otherwise, WITH TZ is added by typmod. */
|
|
*typname = pstrdup("TIMESTAMP");
|
|
break;
|
|
case TIMESTAMPOID:
|
|
*typname = pstrdup("TIMESTAMP");
|
|
break;
|
|
case TIMETZOID:
|
|
if (typemod < 0)
|
|
*typname = pstrdup("TIME WITH TIME ZONE");
|
|
else
|
|
/* otherwise, WITH TZ is added by typmod. */
|
|
*typname = pstrdup("TIME");
|
|
break;
|
|
case TIMEOID:
|
|
*typname = pstrdup("TIME");
|
|
break;
|
|
default:
|
|
|
|
/*
|
|
* No additional processing is required for other types, so get
|
|
* the type name and schema directly from the catalog.
|
|
*/
|
|
*nspid = typeform->typnamespace;
|
|
*typname = pstrdup(NameStr(typeform->typname));
|
|
}
|
|
|
|
if (typemod >= 0)
|
|
*typemodstr = printTypmod("", typemod, typeform->typmodout);
|
|
else
|
|
*typemodstr = pstrdup("");
|
|
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
|
|
/*
|
|
* Return the string representation of the given RELPERSISTENCE value.
|
|
*/
|
|
static inline char* get_persistence_str(char persistence)
|
|
{
|
|
switch (persistence) {
|
|
case RELPERSISTENCE_TEMP:
|
|
return "TEMPORARY";
|
|
case RELPERSISTENCE_UNLOGGED:
|
|
return "UNLOGGED";
|
|
case RELPERSISTENCE_GLOBAL_TEMP:
|
|
return "GLOBAL TEMPORARY";
|
|
case RELPERSISTENCE_PERMANENT:
|
|
return "";
|
|
default:
|
|
elog(ERROR, "unexpected persistence marking %c", persistence);
|
|
return ""; /* make compiler happy */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return the string representation of the given storagetype value.
|
|
*/
|
|
static inline char* get_type_storage(char storagetype)
|
|
{
|
|
switch (storagetype) {
|
|
case 'p':
|
|
return "plain";
|
|
case 'e':
|
|
return "external";
|
|
case 'x':
|
|
return "extended";
|
|
case 'm':
|
|
return "main";
|
|
default:
|
|
elog(ERROR, "invalid storage specifier %c", storagetype);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Allocate a new parameter.
|
|
*/
|
|
static ObjElem* new_object(ObjType type, char *name)
|
|
{
|
|
ObjElem *param;
|
|
|
|
param = (ObjElem*)palloc0(sizeof(ObjElem));
|
|
param->name = name;
|
|
param->objtype = type;
|
|
|
|
return param;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new object parameter.
|
|
*/
|
|
static ObjElem* new_object_object(ObjTree *value)
|
|
{
|
|
ObjElem *param;
|
|
|
|
param = new_object(ObjTypeObject, NULL);
|
|
param->value.object = value;
|
|
|
|
return param;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new object tree to store parameter values.
|
|
*/
|
|
static ObjTree* new_objtree(const char *fmt)
|
|
{
|
|
ObjTree *params;
|
|
|
|
params = (ObjTree*)palloc0(sizeof(ObjTree));
|
|
slist_init(¶ms->params);
|
|
|
|
if (fmt)
|
|
{
|
|
params->fmtinfo = makeStringInfo();
|
|
appendStringInfoString(params->fmtinfo, fmt);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
/*
|
|
* A helper routine to set up %{}D and %{}O elements.
|
|
*
|
|
* Elements "schema_name" and "obj_name" are set. If the namespace OID
|
|
* corresponds to a temp schema, that's set to "pg_temp".
|
|
*
|
|
* The difference between those two element types is whether the obj_name will
|
|
* be quoted as an identifier or not, which is not something that this routine
|
|
* concerns itself with; that will be up to the expand function.
|
|
*/
|
|
static ObjTree* new_objtree_for_qualname(Oid nspid, char *name)
|
|
{
|
|
ObjTree *qualified;
|
|
char *namespc;
|
|
|
|
if (isAnyTempNamespace(nspid))
|
|
namespc = pstrdup("pg_temp");
|
|
else
|
|
namespc = get_namespace_name(nspid);
|
|
|
|
qualified = new_objtree_VA(NULL, 2,
|
|
"schemaname", ObjTypeString, namespc,
|
|
"objname", ObjTypeString, pstrdup(name));
|
|
|
|
return qualified;
|
|
}
|
|
|
|
/*
|
|
* A helper routine to set up %{}D and %{}O elements, with the object specified
|
|
* by classId/objId.
|
|
*/
|
|
static ObjTree* new_objtree_for_qualname_id(Oid classId, Oid objectId)
|
|
{
|
|
ObjTree *qualified;
|
|
Relation catalog;
|
|
HeapTuple catobj;
|
|
Datum obj_nsp;
|
|
Datum obj_name;
|
|
AttrNumber Anum_name;
|
|
AttrNumber Anum_namespace;
|
|
bool isnull;
|
|
|
|
catalog = relation_open(classId, AccessShareLock);
|
|
|
|
catobj = get_catalog_object_by_oid(catalog, objectId);
|
|
if (!catobj)
|
|
elog(ERROR, "cache lookup failed for object with OID %u of catalog \"%s\"",
|
|
objectId, RelationGetRelationName(catalog));
|
|
Anum_name = get_object_attnum_name(classId);
|
|
Anum_namespace = get_object_attnum_namespace(classId);
|
|
|
|
obj_nsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
|
|
&isnull);
|
|
if (isnull)
|
|
elog(ERROR, "null namespace for object %u", objectId);
|
|
|
|
obj_name = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
|
|
&isnull);
|
|
if (isnull)
|
|
elog(ERROR, "null attribute name for object %u", objectId);
|
|
|
|
qualified = new_objtree_for_qualname(DatumGetObjectId(obj_nsp),
|
|
NameStr(*DatumGetName(obj_name)));
|
|
relation_close(catalog, AccessShareLock);
|
|
|
|
return qualified;
|
|
}
|
|
|
|
static ObjTree* new_objtree_for_qualname_rangevar(RangeVar* rv)
|
|
{
|
|
ObjTree *qualified = NULL;
|
|
if (rv->schemaname) {
|
|
qualified = new_objtree_VA(NULL, 2,
|
|
"schemaname", ObjTypeString, rv->schemaname,
|
|
"objname", ObjTypeString, pstrdup(rv->relname));
|
|
} else {
|
|
/* serachpath has no schema set in deparse_utility_command */
|
|
Oid reloid = RangeVarGetRelid(rv, AccessExclusiveLock, false);
|
|
qualified = new_objtree_for_qualname_id(RelationRelationId, reloid);
|
|
}
|
|
return qualified;
|
|
}
|
|
|
|
/*
|
|
* A helper routine to setup %{}T elements.
|
|
*/
|
|
static ObjTree* new_objtree_for_type(Oid typeId, int32 typmod)
|
|
{
|
|
Oid typnspid;
|
|
char *type_nsp;
|
|
char *type_name = NULL;
|
|
char *typmodstr;
|
|
bool type_array;
|
|
|
|
format_type_detailed(typeId, typmod,
|
|
&typnspid, &type_name, &typmodstr, &type_array);
|
|
|
|
if (OidIsValid(typnspid))
|
|
type_nsp = get_namespace_name_or_temp(typnspid);
|
|
else
|
|
type_nsp = pstrdup("");
|
|
|
|
return new_objtree_VA(NULL, 4,
|
|
"schemaname", ObjTypeString, type_nsp,
|
|
"typename", ObjTypeString, type_name,
|
|
"typmod", ObjTypeString, typmodstr,
|
|
"typarray", ObjTypeBool, type_array);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new object tree to store parameter values -- varargs version.
|
|
*
|
|
* The "fmt" argument is used to append as a "fmt" element in the output blob.
|
|
* numobjs indicates the number of extra elements to append; for each one, a
|
|
* name (string), type (from the ObjType enum) and value must be supplied. The
|
|
* value must match the type given; for instance, ObjTypeInteger requires an
|
|
* int64, ObjTypeString requires a char *, ObjTypeArray requires a list (of
|
|
* ObjElem), ObjTypeObject requires an ObjTree, and so on. Each element type *
|
|
* must match the conversion specifier given in the format string, as described
|
|
* in ddl_deparse_expand_command, q.v.
|
|
*
|
|
* Note we don't have the luxury of sprintf-like compiler warnings for
|
|
* malformed argument lists.
|
|
*/
|
|
ObjTree* new_objtree_VA(const char *fmt, int numobjs, ...)
|
|
{
|
|
ObjTree *tree;
|
|
va_list args;
|
|
int i;
|
|
|
|
/* Set up the toplevel object and its "fmt" */
|
|
tree = new_objtree(fmt);
|
|
|
|
/* And process the given varargs */
|
|
va_start(args, numobjs);
|
|
for (i = 0; i < numobjs; i++) {
|
|
char *name;
|
|
ObjType type;
|
|
ObjElem *elem;
|
|
|
|
name = va_arg(args, char *);
|
|
type =(ObjType)va_arg(args, int);
|
|
elem = new_object(type, NULL);
|
|
|
|
/*
|
|
* For all param types other than ObjTypeNull, there must be a value in
|
|
* the varargs. Fetch it and add the fully formed subobject into the
|
|
* main object.
|
|
*/
|
|
switch (type) {
|
|
case ObjTypeNull:
|
|
/* Null params don't have a value (obviously) */
|
|
break;
|
|
case ObjTypeBool:
|
|
elem->value.boolean = va_arg(args, int);
|
|
break;
|
|
case ObjTypeString:
|
|
elem->value.string = va_arg(args, char *);
|
|
break;
|
|
case ObjTypeArray:
|
|
elem->value.array = va_arg(args, List *);
|
|
break;
|
|
case ObjTypeInteger:
|
|
elem->value.integer = va_arg(args, int);
|
|
break;
|
|
case ObjTypeFloat:
|
|
elem->value.flt = va_arg(args, double);
|
|
break;
|
|
case ObjTypeObject:
|
|
elem->value.object = va_arg(args, ObjTree *);
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid ObjTree element type %d", type);
|
|
}
|
|
|
|
elem->name = name;
|
|
append_premade_object(tree, elem);
|
|
}
|
|
|
|
va_end(args);
|
|
return tree;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new string object.
|
|
*/
|
|
ObjElem* new_string_object(char *value)
|
|
{
|
|
ObjElem *param;
|
|
|
|
Assert(value);
|
|
|
|
param = new_object(ObjTypeString, NULL);
|
|
param->value.string = value;
|
|
|
|
return param;
|
|
}
|
|
|
|
static ObjTree* deparse_AlterSchemaStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
AlterSchemaStmt *stmt = (AlterSchemaStmt *) parsetree;
|
|
|
|
bool setblockchain = false;
|
|
|
|
if (stmt->charset == PG_INVALID_ENCODING && !stmt->collate) {
|
|
setblockchain = true;
|
|
}
|
|
|
|
ret = new_objtree_VA("ALTER SCHEMA %{schemaname}I", 1,
|
|
"schemaname", ObjTypeString, stmt->schemaname);
|
|
|
|
if (setblockchain) {
|
|
append_string_object(ret, "%{with}s BLOCKCHAIN", "with", stmt->hasBlockChain ?
|
|
"WITH" : "WITHOUT");
|
|
} else {
|
|
if (stmt->charset != PG_INVALID_ENCODING) {
|
|
append_string_object(ret, "CHARACTER SET = %{charset}s", "charset",
|
|
pg_encoding_to_char(stmt->charset));
|
|
}
|
|
|
|
if (stmt->collate) {
|
|
append_string_object(ret, "COLLATE = %{collate}s", "collate", stmt->collate);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Deparse a CreateSchemaStmt.
|
|
*
|
|
* Given a schema OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s
|
|
*/
|
|
static ObjTree* deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
|
|
ObjTree *ret;
|
|
ObjTree *auth;
|
|
ObjTree *blockchain;
|
|
ret = new_objtree_VA("CREATE SCHEMA %{if_not_exists}s %{name}I", 2,
|
|
"if_not_exists", ObjTypeString,
|
|
node->missing_ok ? "IF NOT EXISTS" : "",
|
|
"name", ObjTypeString,
|
|
node->schemaname ? node->schemaname : "");
|
|
|
|
auth = new_objtree("AUTHORIZATION");
|
|
if (node->authid)
|
|
append_string_object(auth, "%{authorization_role}I",
|
|
"authorization_role",
|
|
node->authid);
|
|
else
|
|
append_not_present(auth, "%{authorization_role}I");
|
|
|
|
append_object_object(ret, "%{authorization}s", auth);
|
|
|
|
blockchain = new_objtree("WITH BLOCKCHAIN");
|
|
if (!node->hasBlockChain)
|
|
append_not_present(blockchain, "%{blockchain}s");
|
|
append_object_object(ret, "%{blockchain}s", blockchain);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return the given object type as a string.
|
|
*
|
|
* If isgrant is true, then this function is called while deparsing GRANT
|
|
* statement and some object names are replaced.
|
|
*/
|
|
const char* string_objtype(ObjectType objtype, bool isgrant)
|
|
{
|
|
switch (objtype) {
|
|
case OBJECT_COLUMN:
|
|
return isgrant ? "TABLE" : "COLUMN";
|
|
case OBJECT_DOMAIN:
|
|
return "DOMAIN";
|
|
case OBJECT_FUNCTION:
|
|
return "FUNCTION";
|
|
case OBJECT_INDEX:
|
|
return "INDEX";
|
|
case OBJECT_SCHEMA:
|
|
return "SCHEMA";
|
|
case OBJECT_SEQUENCE:
|
|
return "SEQUENCE";
|
|
case OBJECT_LARGE_SEQUENCE:
|
|
return "LARGE SEQUENCE";
|
|
case OBJECT_TABLE:
|
|
return "TABLE";
|
|
case OBJECT_TABLESPACE:
|
|
return "TABLESPACE";
|
|
case OBJECT_TRIGGER:
|
|
return "TRIGGER";
|
|
case OBJECT_TYPE:
|
|
return "TYPE";
|
|
case OBJECT_VIEW:
|
|
return "VIEW";
|
|
default:
|
|
elog(WARNING, "unsupported object type %d for string", objtype);
|
|
}
|
|
|
|
return "???"; /* keep compiler quiet */
|
|
}
|
|
|
|
/*
|
|
* Process the pre-built format string from the ObjTree into the output parse
|
|
* state.
|
|
*/
|
|
static void objtree_fmt_to_jsonb_element(JsonbParseState *state, ObjTree *tree)
|
|
{
|
|
JsonbValue key;
|
|
JsonbValue val;
|
|
|
|
if (tree->fmtinfo == NULL)
|
|
return;
|
|
|
|
/* Push the key first */
|
|
key.type = jbvString;
|
|
key.string.val = "fmt";
|
|
key.string.len = strlen(key.string.val);
|
|
key.estSize = sizeof(JEntry) + key.string.len;
|
|
pushJsonbValue(&state, WJB_KEY, &key);
|
|
|
|
/* Then process the pre-built format string */
|
|
val.type = jbvString;
|
|
val.string.len = tree->fmtinfo->len;
|
|
val.string.val = tree->fmtinfo->data;
|
|
val.estSize = sizeof(JEntry) + val.string.len;
|
|
pushJsonbValue(&state, WJB_VALUE, &val);
|
|
}
|
|
|
|
/*
|
|
* Process the role string into the output parse state.
|
|
*/
|
|
static void role_to_jsonb_element(JsonbParseState *state, char *owner)
|
|
{
|
|
JsonbValue key;
|
|
JsonbValue val;
|
|
|
|
if (owner == NULL)
|
|
return;
|
|
|
|
/* Push the key first */
|
|
key.type = jbvString;
|
|
key.string.val = "myowner";
|
|
key.string.len = strlen(key.string.val);
|
|
key.estSize = sizeof(JEntry) + key.string.len;
|
|
pushJsonbValue(&state, WJB_KEY, &key);
|
|
|
|
/* Then process the role string */
|
|
val.type = jbvString;
|
|
val.string.len = strlen(owner);
|
|
val.string.val = owner;
|
|
val.estSize = sizeof(JEntry) + val.string.len;
|
|
pushJsonbValue(&state, WJB_VALUE, &val);
|
|
}
|
|
|
|
/*
|
|
* Create a JSONB representation from an ObjTree and its owner (if given).
|
|
*/
|
|
static Jsonb* objtree_to_jsonb(ObjTree *tree, char *owner)
|
|
{
|
|
JsonbValue *value;
|
|
|
|
value = objtree_to_jsonb_rec(tree, NULL, owner);
|
|
return JsonbValueToJsonb(value);
|
|
}
|
|
|
|
/*
|
|
* Helper for objtree_to_jsonb: process an individual element from an object or
|
|
* an array into the output parse state.
|
|
*/
|
|
static void objtree_to_jsonb_element(JsonbParseState *state, ObjElem *object,
|
|
int elem_token)
|
|
{
|
|
JsonbValue val;
|
|
|
|
switch (object->objtype) {
|
|
case ObjTypeNull:
|
|
val.type = jbvNull;
|
|
val.estSize = sizeof(JEntry);
|
|
pushJsonbValue(&state, elem_token, &val);
|
|
break;
|
|
|
|
case ObjTypeString:
|
|
val.type = jbvString;
|
|
if (!object->value.string) {
|
|
elog(ERROR, "ObjTypeString value should not be empty");
|
|
}
|
|
val.string.len = strlen(object->value.string);
|
|
val.string.val = object->value.string;
|
|
val.estSize = sizeof(JEntry) + val.string.len;
|
|
pushJsonbValue(&state, elem_token, &val);
|
|
break;
|
|
|
|
case ObjTypeInteger:
|
|
val.type = jbvNumeric;
|
|
val.numeric = (Numeric)
|
|
DatumGetNumeric(DirectFunctionCall1(int8_numeric,
|
|
object->value.integer));
|
|
val.estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val.numeric);
|
|
pushJsonbValue(&state, elem_token, &val);
|
|
break;
|
|
|
|
case ObjTypeFloat:
|
|
val.type = jbvNumeric;
|
|
val.numeric = (Numeric)
|
|
DatumGetNumeric(DirectFunctionCall1(float8_numeric,
|
|
object->value.flt));
|
|
val.estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val.numeric);
|
|
pushJsonbValue(&state, elem_token, &val);
|
|
break;
|
|
|
|
case ObjTypeBool:
|
|
val.type = jbvBool;
|
|
val.boolean = object->value.boolean;
|
|
val.estSize = sizeof(JEntry);
|
|
pushJsonbValue(&state, elem_token, &val);
|
|
break;
|
|
|
|
case ObjTypeObject:
|
|
/* Recursively add the object into the existing parse state */
|
|
if (!object->value.object) {
|
|
elog(ERROR, "ObjTypeObject value should not be empty");
|
|
}
|
|
objtree_to_jsonb_rec(object->value.object, state, NULL);
|
|
break;
|
|
|
|
case ObjTypeArray: {
|
|
ListCell *cell;
|
|
if (!object->value.array) {
|
|
elog(ERROR, "ObjTypeArray value should not be empty");
|
|
}
|
|
pushJsonbValue(&state, WJB_BEGIN_ARRAY, NULL);
|
|
foreach(cell, object->value.array) {
|
|
ObjElem *elem = (ObjElem*)lfirst(cell);
|
|
|
|
objtree_to_jsonb_element(state, elem, WJB_ELEM);
|
|
}
|
|
pushJsonbValue(&state, WJB_END_ARRAY, NULL);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "unrecognized object type %d", object->objtype);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recursive helper for objtree_to_jsonb.
|
|
*/
|
|
static JsonbValue* objtree_to_jsonb_rec(ObjTree *tree, JsonbParseState *state, char *owner)
|
|
{
|
|
slist_iter iter;
|
|
|
|
pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
|
|
|
|
role_to_jsonb_element(state, owner);
|
|
objtree_fmt_to_jsonb_element(state, tree);
|
|
|
|
slist_foreach(iter, &tree->params) {
|
|
ObjElem *object = slist_container(ObjElem, node, iter.cur);
|
|
JsonbValue key;
|
|
|
|
/* Push the key first */
|
|
key.type = jbvString;
|
|
key.string.len = strlen(object->name);
|
|
key.string.val = object->name;
|
|
key.estSize = sizeof(JEntry) + key.string.len;
|
|
pushJsonbValue(&state, WJB_KEY, &key);
|
|
|
|
/* Then process the value according to its type */
|
|
objtree_to_jsonb_element(state, object, WJB_VALUE);
|
|
}
|
|
|
|
return pushJsonbValue(&state, WJB_END_OBJECT, NULL);
|
|
}
|
|
|
|
/*
|
|
* Subroutine for CREATE TABLE/CREATE DOMAIN deparsing.
|
|
*
|
|
* Given a table OID or domain OID, obtain its constraints and append them to
|
|
* the given elements list. The updated list is returned.
|
|
*
|
|
* This works for typed tables, regular tables, and domains.
|
|
*
|
|
* Note that CONSTRAINT_FOREIGN constraints are always ignored.
|
|
*/
|
|
static List* obtainConstraints(List *elements, Oid relationId)
|
|
{
|
|
Relation conRel;
|
|
ScanKeyData skey[1];
|
|
SysScanDesc scan;
|
|
HeapTuple tuple;
|
|
ObjTree *constr;
|
|
|
|
/* Only one may be valid */
|
|
Assert(OidIsValid(relationId));
|
|
|
|
/*
|
|
* Scan pg_constraint to fetch all constraints linked to the given
|
|
* relation.
|
|
*/
|
|
conRel = relation_open(ConstraintRelationId, AccessShareLock);
|
|
ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
|
|
F_OIDEQ, ObjectIdGetDatum(relationId));
|
|
scan = systable_beginscan(conRel, ConstraintRelidIndexId, true, NULL, 1, skey);
|
|
|
|
/*
|
|
* For each constraint, add a node to the list of table elements. In
|
|
* these nodes we include not only the printable information ("fmt"), but
|
|
* also separate attributes to indicate the type of constraint, for
|
|
* automatic processing.
|
|
*/
|
|
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
|
{
|
|
Form_pg_constraint constrForm;
|
|
char *contype = NULL;
|
|
|
|
constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
|
|
|
|
switch (constrForm->contype) {
|
|
case CONSTRAINT_CHECK:
|
|
contype = "check";
|
|
break;
|
|
case CONSTRAINT_FOREIGN:
|
|
continue; /* not here */
|
|
case CONSTRAINT_PRIMARY:
|
|
contype = "primary key";
|
|
break;
|
|
case CONSTRAINT_UNIQUE:
|
|
contype = "unique";
|
|
break;
|
|
case CONSTRAINT_TRIGGER:
|
|
contype = "trigger";
|
|
break;
|
|
case CONSTRAINT_EXCLUSION:
|
|
contype = "exclusion";
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized constraint type %c", constrForm->contype);
|
|
}
|
|
|
|
/*
|
|
* "type" and "contype" are not part of the printable output, but are
|
|
* useful to programmatically distinguish these from columns and among
|
|
* different constraint types.
|
|
*
|
|
* XXX it might be useful to also list the column names in a PK, etc.
|
|
*/
|
|
constr = new_objtree_VA("CONSTRAINT %{name}I %{definition}s", 4,
|
|
"type", ObjTypeString, "constraint",
|
|
"contype", ObjTypeString, contype,
|
|
"name", ObjTypeString, NameStr(constrForm->conname),
|
|
"definition", ObjTypeString,
|
|
pg_get_constraintdef_part_string(HeapTupleGetOid(tuple)));
|
|
|
|
if (constrForm->conindid &&
|
|
(constrForm->contype == CONSTRAINT_PRIMARY ||
|
|
constrForm->contype == CONSTRAINT_UNIQUE ||
|
|
constrForm->contype == CONSTRAINT_EXCLUSION)) {
|
|
Oid tblspc = get_rel_tablespace(constrForm->conindid);
|
|
|
|
if (OidIsValid(tblspc)) {
|
|
char *tblspcname = get_tablespace_name(tblspc);
|
|
|
|
if (!tblspcname) {
|
|
elog(ERROR, "cache lookup failed for tablespace %u", tblspc);
|
|
}
|
|
|
|
append_string_object(constr, "USING INDEX TABLESPACE %{tblspc}s", "tblspc", tblspcname);
|
|
}
|
|
}
|
|
|
|
elements = lappend(elements, new_object_object(constr));
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
relation_close(conRel, AccessShareLock);
|
|
|
|
return elements;
|
|
}
|
|
|
|
/*
|
|
* Return an index definition, split into several pieces.
|
|
*
|
|
* A large amount of code is duplicated from pg_get_indexdef_worker, but
|
|
* control flow is different enough that it doesn't seem worth keeping them
|
|
* together.
|
|
*/
|
|
static void pg_get_indexdef_detailed(Oid indexrelid, bool global,
|
|
char **index_am,
|
|
char **definition,
|
|
char **reloptions,
|
|
char **tablespace,
|
|
char **whereClause,
|
|
bool *invisible)
|
|
{
|
|
HeapTuple ht_idx;
|
|
HeapTuple ht_idxrel;
|
|
HeapTuple ht_am;
|
|
Form_pg_index idxrec;
|
|
Form_pg_class idxrelrec;
|
|
Form_pg_am amrec;
|
|
List *indexprs;
|
|
ListCell *indexpr_item;
|
|
List *context;
|
|
Oid indrelid;
|
|
int keyno;
|
|
Datum indcollDatum;
|
|
Datum indclassDatum;
|
|
Datum indoptionDatum;
|
|
bool isnull;
|
|
oidvector *indcollation;
|
|
oidvector *indclass;
|
|
int2vector *indoption;
|
|
StringInfoData definitionBuf;
|
|
int indnkeyatts;
|
|
|
|
*tablespace = NULL;
|
|
*whereClause = NULL;
|
|
|
|
/* Fetch the pg_index tuple by the Oid of the index */
|
|
ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
|
|
if (!HeapTupleIsValid(ht_idx))
|
|
elog(ERROR, "cache lookup failed for index with OID %u", indexrelid);
|
|
idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
|
|
|
|
indrelid = idxrec->indrelid;
|
|
Assert(indexrelid == idxrec->indexrelid);
|
|
indnkeyatts = GetIndexKeyAttsByTuple(NULL, ht_idx);
|
|
|
|
/* Must get indcollation, indclass, and indoption the hard way */
|
|
indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
|
|
Anum_pg_index_indcollation, &isnull);
|
|
Assert(!isnull);
|
|
indcollation = (oidvector *) DatumGetPointer(indcollDatum);
|
|
|
|
indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
|
|
Anum_pg_index_indclass, &isnull);
|
|
Assert(!isnull);
|
|
indclass = (oidvector *) DatumGetPointer(indclassDatum);
|
|
|
|
indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
|
|
Anum_pg_index_indoption, &isnull);
|
|
Assert(!isnull);
|
|
indoption = (int2vector *) DatumGetPointer(indoptionDatum);
|
|
|
|
/* Fetch the pg_class tuple of the index relation */
|
|
ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
|
|
if (!HeapTupleIsValid(ht_idxrel))
|
|
elog(ERROR, "cache lookup failed for relation with OID %u", indexrelid);
|
|
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
|
|
|
|
/* Fetch the pg_am tuple of the index' access method */
|
|
ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
|
|
if (!HeapTupleIsValid(ht_am))
|
|
elog(ERROR, "cache lookup failed for access method with OID %u",
|
|
idxrelrec->relam);
|
|
amrec = (Form_pg_am) GETSTRUCT(ht_am);
|
|
|
|
/*
|
|
* Get the index expressions, if any. (NOTE: we do not use the relcache
|
|
* versions of the expressions and predicate, because we want to display
|
|
* non-const-folded expressions.)
|
|
*/
|
|
if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) {
|
|
Datum exprsDatum;
|
|
char *exprsString;
|
|
|
|
exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
|
|
Anum_pg_index_indexprs, &isnull);
|
|
Assert(!isnull);
|
|
exprsString = TextDatumGetCString(exprsDatum);
|
|
indexprs = (List *) stringToNode(exprsString);
|
|
pfree(exprsString);
|
|
}
|
|
else {
|
|
indexprs = NIL;
|
|
}
|
|
|
|
indexpr_item = list_head(indexprs);
|
|
|
|
context = deparse_context_for(get_rel_name(indrelid), indrelid);
|
|
|
|
initStringInfo(&definitionBuf);
|
|
|
|
/* Output index AM */
|
|
*index_am = pstrdup(quote_identifier(NameStr(amrec->amname)));
|
|
|
|
/*
|
|
* Output index definition. Note the outer parens must be supplied by
|
|
* caller.
|
|
*/
|
|
bool add_global = false;
|
|
appendStringInfoString(&definitionBuf, "(");
|
|
for (keyno = 0; keyno < idxrec->indnatts; keyno++) {
|
|
AttrNumber attnum = idxrec->indkey.values[keyno];
|
|
int16 opt = indoption->values[keyno];
|
|
Oid keycoltype;
|
|
Oid keycolcollation;
|
|
|
|
/* Print INCLUDE to divide key and non-key attrs. */
|
|
if (keyno == indnkeyatts) {
|
|
if (global) {
|
|
appendStringInfoString(&definitionBuf, ") GLOBAL ");
|
|
add_global = true;
|
|
} else {
|
|
appendStringInfoString(&definitionBuf, ") ");
|
|
}
|
|
appendStringInfoString(&definitionBuf, "INCLUDE (");
|
|
}
|
|
else {
|
|
appendStringInfoString(&definitionBuf, keyno == 0 ? "" : ", ");
|
|
}
|
|
|
|
if (attnum != 0) {
|
|
/* Simple index column */
|
|
char *attname;
|
|
int32 keycoltypmod;
|
|
|
|
attname = get_attname(indrelid, attnum);
|
|
appendStringInfoString(&definitionBuf, quote_identifier(attname));
|
|
get_atttypetypmodcoll(indrelid, attnum,
|
|
&keycoltype, &keycoltypmod,
|
|
&keycolcollation);
|
|
} else {
|
|
/* Expressional index */
|
|
Node *indexkey;
|
|
char *str;
|
|
|
|
if (indexpr_item == NULL)
|
|
elog(ERROR, "too few entries in indexprs list");
|
|
indexkey = (Node *) lfirst(indexpr_item);
|
|
indexpr_item = lnext(indexpr_item);
|
|
|
|
/* Deparse */
|
|
str = deparse_expression(indexkey, context, false, false);
|
|
|
|
/* Need parens if it's not a bare function call */
|
|
if (indexkey && IsA(indexkey, FuncExpr) &&
|
|
((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL) {
|
|
appendStringInfoString(&definitionBuf, str);
|
|
} else if (indexkey && IsA(indexkey, PrefixKey)) {
|
|
appendStringInfoString(&definitionBuf, str);
|
|
} else {
|
|
appendStringInfo(&definitionBuf, "(%s)", str);
|
|
}
|
|
|
|
keycoltype = exprType(indexkey);
|
|
keycolcollation = exprCollation(indexkey);
|
|
}
|
|
|
|
/* Print additional decoration for (selected) key columns, even if default */
|
|
if (keyno < indnkeyatts) {
|
|
Oid indcoll = indcollation->values[keyno];
|
|
if (OidIsValid(indcoll))
|
|
appendStringInfo(&definitionBuf, " COLLATE %s",
|
|
generate_collation_name((indcoll)));
|
|
|
|
/* Add the operator class name, even if default */
|
|
get_opclass_name(indclass->values[keyno], InvalidOid, &definitionBuf);
|
|
|
|
/* Add options if relevant */
|
|
if (amrec->amcanorder) {
|
|
/* If it supports sort ordering, report DESC and NULLS opts */
|
|
if (opt & INDOPTION_DESC) {
|
|
appendStringInfoString(&definitionBuf, " DESC");
|
|
/* NULLS FIRST is the default in this case */
|
|
if (!(opt & INDOPTION_NULLS_FIRST))
|
|
appendStringInfoString(&definitionBuf, " NULLS LAST");
|
|
} else {
|
|
if (opt & INDOPTION_NULLS_FIRST)
|
|
appendStringInfoString(&definitionBuf, " NULLS FIRST");
|
|
}
|
|
}
|
|
|
|
/* XXX excludeOps thingy was here; do we need anything? */
|
|
}
|
|
}
|
|
|
|
appendStringInfoString(&definitionBuf, ")");
|
|
if (!add_global && global) {
|
|
appendStringInfoString(&definitionBuf, "GLOBAL");
|
|
}
|
|
|
|
if (idxrelrec->parttype == PARTTYPE_PARTITIONED_RELATION &&
|
|
idxrelrec->relkind != RELKIND_GLOBAL_INDEX) {
|
|
pg_get_indexdef_partitions(indexrelid, idxrec, true, &definitionBuf, true, true, true);
|
|
}
|
|
|
|
*definition = definitionBuf.data;
|
|
|
|
/* Output reloptions */
|
|
*reloptions = flatten_reloptions(indexrelid);
|
|
|
|
/* Output tablespace */
|
|
{
|
|
Oid tblspc;
|
|
|
|
tblspc = get_rel_tablespace(indexrelid);
|
|
if (OidIsValid(tblspc))
|
|
*tablespace = pstrdup(quote_identifier(get_tablespace_name(tblspc)));
|
|
}
|
|
|
|
*invisible = false;
|
|
if (!GetIndexVisibleStateByTuple(ht_idx)) {
|
|
*invisible = true;
|
|
}
|
|
|
|
/* Report index predicate, if any */
|
|
if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
|
|
{
|
|
Node *node;
|
|
Datum predDatum;
|
|
char *predString;
|
|
|
|
/* Convert text string to node tree */
|
|
predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
|
|
Anum_pg_index_indpred, &isnull);
|
|
Assert(!isnull);
|
|
predString = TextDatumGetCString(predDatum);
|
|
node = (Node *) stringToNode(predString);
|
|
pfree(predString);
|
|
|
|
/* Deparse */
|
|
*whereClause = deparse_expression(node, context, false, false);
|
|
}
|
|
|
|
/* Clean up */
|
|
ReleaseSysCache(ht_idx);
|
|
ReleaseSysCache(ht_idxrel);
|
|
ReleaseSysCache(ht_am);
|
|
}
|
|
|
|
/*
|
|
* Obtain the deparsed default value for the given column of the given table.
|
|
*
|
|
* Caller must have set a correct deparse context.
|
|
*/
|
|
static char* RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext,
|
|
List **exprs)
|
|
{
|
|
Node *defval;
|
|
char *defstr;
|
|
|
|
defval = build_column_default(rel, attno);
|
|
defstr = deparse_expression(defval, dpcontext, false, false);
|
|
|
|
/* Collect the expression for later replication safety checks */
|
|
if (exprs)
|
|
*exprs = lappend(*exprs, defval);
|
|
|
|
return defstr;
|
|
}
|
|
|
|
static char *RelationGetColumnOnUpdate(Node *update_expr, List *dpcontext, List **exprs)
|
|
{
|
|
StringInfoData buf;
|
|
|
|
initStringInfo(&buf);
|
|
|
|
if (IsA(update_expr, FuncCall)) {
|
|
FuncCall *n = (FuncCall*)update_expr;
|
|
Value *funcname = (Value*)(lsecond(n->funcname));
|
|
if (!pg_strcasecmp(strVal(funcname), "text_date")) {
|
|
appendStringInfo(&buf, "CURRENT_DATE");
|
|
} else if (!pg_strcasecmp(strVal(funcname), "pg_systimestamp")) {
|
|
appendStringInfo(&buf, "CURRENT_TIMESTAMP");
|
|
} else if (!pg_strcasecmp(strVal(funcname), "sysdate")) {
|
|
appendStringInfo(&buf, "SYSDATE");
|
|
} else if (u_sess->attr.attr_sql.dolphin) {
|
|
appendStringInfo(&buf, "%s", n->colname);
|
|
}
|
|
} else if (IsA(update_expr, TypeCast)) {
|
|
TypeCast *n = (TypeCast*)update_expr;
|
|
TypeName *t = n->typname;
|
|
Value *typname = (Value*)(lsecond(t->names));
|
|
if (!pg_strcasecmp(strVal(typname), "timetz")) {
|
|
appendStringInfo(&buf, "CURRENT_TIME");
|
|
} else if (!pg_strcasecmp(strVal(typname), "timestamptz")) {
|
|
appendStringInfo(&buf, "CURRENT_TIMESTAMP");
|
|
} else if (!pg_strcasecmp(strVal(typname), "time")) {
|
|
appendStringInfo(&buf, "LOCALTIME");
|
|
} else if (!pg_strcasecmp(strVal(typname), "timestamp")) {
|
|
appendStringInfo(&buf, "LOCALTIMESTAMP");
|
|
}
|
|
|
|
if (t->typmods) {
|
|
A_Const* typmod = (A_Const*)(linitial(t->typmods));
|
|
appendStringInfo(&buf, "(%ld)", intVal(&typmod->val));
|
|
}
|
|
} else if (IsA(update_expr, A_Expr)) {
|
|
A_Expr *a = (A_Expr*)update_expr;
|
|
Value *n = (Value*)linitial(a->name);
|
|
if (a->kind == AEXPR_OP && (!pg_strcasecmp(strVal(n), "+") ||
|
|
!pg_strcasecmp(strVal(n), "-"))) {
|
|
if (a->rexpr && IsA(a->rexpr, A_Const)) {
|
|
char *larg = RelationGetColumnOnUpdate(a->lexpr, dpcontext, exprs);
|
|
A_Const *con = (A_Const*)a->rexpr;
|
|
Value *v = &con->val;
|
|
if (nodeTag(v) == T_Integer) {
|
|
appendStringInfo(&buf, "%s %s %ld", larg, strVal(n), intVal(v));
|
|
} else if (nodeTag(v) == T_String) {
|
|
appendStringInfo(&buf, "%s %s '%s'", larg, strVal(n), strVal(v));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
/* used by AT_ModifyColumn */
|
|
static ObjTree* deparse_ColumnDef_constraints(ObjTree *ret, Relation relation,
|
|
ColumnDef *coldef, List *dpcontext, List **exprs)
|
|
{
|
|
ObjTree *tmp_obj;
|
|
Oid relid = RelationGetRelid(relation);
|
|
ListCell *cell;
|
|
HeapTuple attrTup;
|
|
Form_pg_attribute attrForm;
|
|
|
|
bool saw_notnull = false;
|
|
bool saw_autoincrement = false;
|
|
char* onupdate = NULL;
|
|
attrTup = SearchSysCacheAttName(relid, coldef->colname);
|
|
if (!HeapTupleIsValid(attrTup))
|
|
elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
|
|
coldef->colname, relid);
|
|
attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
|
|
|
|
foreach(cell, coldef->constraints) {
|
|
Constraint *constr = (Constraint *) lfirst(cell);
|
|
|
|
if (constr->contype == CONSTR_NOTNULL) {
|
|
saw_notnull = true;
|
|
} else if (constr->contype == CONSTR_AUTO_INCREMENT) {
|
|
saw_autoincrement = true;
|
|
check_alter_table_rewrite_replident_change(relation, attrForm->attnum, "MODIFY COLUMN AUTO_INCREMENT");
|
|
} else if (constr->contype == CONSTR_DEFAULT && constr->update_expr) {
|
|
onupdate = RelationGetColumnOnUpdate(constr->update_expr, dpcontext, exprs);
|
|
}
|
|
}
|
|
|
|
if (coldef->is_not_null)
|
|
saw_notnull = true;
|
|
|
|
if (saw_autoincrement) {
|
|
ReleaseSysCache(attrTup);
|
|
return ret;
|
|
}
|
|
|
|
append_string_object(ret, "%{auto_increment}s", "auto_increment",
|
|
saw_autoincrement ? "AUTO_INCREMENT" : "");
|
|
|
|
/* ON UPDATE */
|
|
append_string_object(ret, "ON UPDATE %{on_update}s", "on_update", onupdate ? onupdate : "");
|
|
|
|
append_string_object(ret, "%{not_null}s", "not_null",
|
|
saw_notnull ? "NOT NULL" : saw_autoincrement ? "NULL" : "");
|
|
|
|
/* GENERATED COLUMN EXPRESSION */
|
|
tmp_obj = new_objtree("GENERATED ALWAYS AS");
|
|
if (coldef->generatedCol == ATTRIBUTE_GENERATED_STORED) {
|
|
char *defstr;
|
|
|
|
defstr = RelationGetColumnDefault(relation, attrForm->attnum, dpcontext, exprs);
|
|
append_string_object(tmp_obj, "(%{generation_expr}s) STORED", "generation_expr", defstr);
|
|
} else {
|
|
append_not_present(tmp_obj, "(%{generation_expr}s) STORED");
|
|
}
|
|
|
|
append_object_object(ret, "%{generated_column}s", tmp_obj);
|
|
|
|
tmp_obj = new_objtree("DEFAULT");
|
|
|
|
if (attrForm->atthasdef &&
|
|
coldef->generatedCol != ATTRIBUTE_GENERATED_STORED &&
|
|
!saw_autoincrement &&
|
|
!onupdate) {
|
|
char *defstr;
|
|
|
|
defstr = RelationGetColumnDefault(relation, attrForm->attnum,
|
|
dpcontext, exprs);
|
|
|
|
append_string_object(tmp_obj, "%{default}s", "default", defstr);
|
|
} else {
|
|
append_not_present(tmp_obj, "%{default}s");
|
|
}
|
|
append_object_object(ret, "%{default}s", tmp_obj);
|
|
|
|
ReleaseSysCache(attrTup);
|
|
return ret;
|
|
}
|
|
|
|
static bool istypestring(Oid typid);
|
|
/*
|
|
* Deparse a ColumnDef node within a regular (non-typed) table creation.
|
|
*
|
|
* NOT NULL constraints in the column definition are emitted directly in the
|
|
* column definition by this routine; other constraints must be emitted
|
|
* elsewhere (the info in the parse node is incomplete anyway).
|
|
*
|
|
* Verbose syntax
|
|
* %{name}I %{coltype}T %{auto_increment} %{default}s %{not_null}s %{collation}s
|
|
*/
|
|
static ObjTree* deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
|
|
ColumnDef *coldef, bool is_alter, List **exprs)
|
|
{
|
|
ObjTree *ret;
|
|
ObjTree *tmp_obj;
|
|
Oid relid = RelationGetRelid(relation);
|
|
HeapTuple attrTup;
|
|
Form_pg_attribute attrForm;
|
|
Oid typid;
|
|
int32 typmod;
|
|
Oid typcollation;
|
|
bool saw_notnull;
|
|
bool saw_autoincrement;
|
|
char* onupdate = NULL;
|
|
ListCell *cell;
|
|
|
|
/*
|
|
* Inherited columns without local definitions must not be emitted.
|
|
*
|
|
* XXX maybe it is useful to have them with "present = false" or some
|
|
* such?
|
|
*/
|
|
if (!coldef->is_local)
|
|
return NULL;
|
|
|
|
attrTup = SearchSysCacheAttName(relid, coldef->colname);
|
|
if (!HeapTupleIsValid(attrTup))
|
|
elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
|
|
coldef->colname, relid);
|
|
attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
|
|
|
|
get_atttypetypmodcoll(relid, attrForm->attnum,
|
|
&typid, &typmod, &typcollation);
|
|
|
|
ret = new_objtree_VA("%{name}I %{coltype}T", 3,
|
|
"type", ObjTypeString, "column",
|
|
"name", ObjTypeString, coldef->colname,
|
|
"coltype", ObjTypeObject,
|
|
new_objtree_for_type(typid, typmod));
|
|
|
|
tmp_obj = new_objtree("COLLATE");
|
|
if (OidIsValid(typcollation)) {
|
|
append_object_object(tmp_obj, "%{name}D",
|
|
new_objtree_for_qualname_id(CollationRelationId,
|
|
typcollation));
|
|
} else {
|
|
append_not_present(tmp_obj, "%{name}D");
|
|
}
|
|
append_object_object(ret, "%{collation}s", tmp_obj);
|
|
|
|
if (!composite) {
|
|
/*
|
|
* Emit a NOT NULL declaration if necessary. Note that we cannot
|
|
* trust pg_attribute.attnotnull here, because that bit is also set
|
|
* when primary keys are specified; we must not emit a NOT NULL
|
|
* constraint in that case, unless explicitly specified. Therefore,
|
|
* we scan the list of constraints attached to this column to
|
|
* determine whether we need to emit anything. (Fortunately, NOT NULL
|
|
* constraints cannot be table constraints.)
|
|
*
|
|
* In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
|
|
* marked is_not_null.
|
|
*/
|
|
saw_notnull = false;
|
|
saw_autoincrement = false;
|
|
foreach(cell, coldef->constraints) {
|
|
Constraint *constr = (Constraint *) lfirst(cell);
|
|
|
|
if (constr->contype == CONSTR_NOTNULL) {
|
|
saw_notnull = true;
|
|
} else if (constr->contype == CONSTR_AUTO_INCREMENT) {
|
|
saw_autoincrement = true;
|
|
} else if (constr->contype == CONSTR_DEFAULT && constr->update_expr) {
|
|
onupdate = RelationGetColumnOnUpdate(constr->update_expr, dpcontext, exprs);
|
|
}
|
|
}
|
|
|
|
if (is_alter && coldef->is_not_null)
|
|
saw_notnull = true;
|
|
|
|
if (is_alter && !saw_autoincrement && coldef->raw_default &&
|
|
IsA(coldef->raw_default, AutoIncrement)) {
|
|
saw_autoincrement = true;
|
|
}
|
|
|
|
if (is_alter && saw_autoincrement) {
|
|
check_alter_table_rewrite_replident_change(relation, attrForm->attnum, "ADD COLUMN AUTO_INCREMENT");
|
|
/* auto_increment will be set with constraint when rewrite finish */
|
|
ReleaseSysCache(attrTup);
|
|
return ret;
|
|
}
|
|
|
|
append_string_object(ret, "%{auto_increment}s", "auto_increment",
|
|
saw_autoincrement ? "AUTO_INCREMENT" : "");
|
|
|
|
tmp_obj = new_objtree("DEFAULT");
|
|
|
|
if (attrForm->atthasdef &&
|
|
coldef->generatedCol != ATTRIBUTE_GENERATED_STORED &&
|
|
!saw_autoincrement) {
|
|
char *defstr = NULL;
|
|
|
|
/* initdefval intend that default value is a constant expr,
|
|
* if the default can not get from initdefval, then need output dml change
|
|
* and set default after rewrite
|
|
*/
|
|
if (is_alter) {
|
|
if (coldef->initdefval) {
|
|
StringInfoData defvalbuf;
|
|
initStringInfo(&defvalbuf);
|
|
if (istypestring(typid))
|
|
appendStringInfo(&defvalbuf, "\'%s\'", coldef->initdefval);
|
|
else
|
|
appendStringInfo(&defvalbuf, "%s", coldef->initdefval);
|
|
defstr = pstrdup(defvalbuf.data);
|
|
} else {
|
|
/* if coldef->initdefval not exist, then default is not a constant
|
|
* handle it after rewrite finish
|
|
*/
|
|
append_not_present(tmp_obj, "%{default}s");
|
|
}
|
|
append_string_object(tmp_obj, "%{default}s", "default", defstr);
|
|
} else {
|
|
defstr = RelationGetColumnDefault(relation, attrForm->attnum, dpcontext, exprs);
|
|
if (defstr == NULL || defstr[0] == '\0') {
|
|
append_not_present(tmp_obj, "%{default}s");
|
|
} else {
|
|
append_string_object(tmp_obj, "%{default}s", "default", defstr);
|
|
}
|
|
}
|
|
} else {
|
|
append_not_present(tmp_obj, "%{default}s");
|
|
}
|
|
append_object_object(ret, "%{default}s", tmp_obj);
|
|
|
|
/* ON UPDATE */
|
|
if ((!onupdate || !strlen(onupdate)) && coldef->update_default) {
|
|
onupdate = RelationGetColumnOnUpdate(coldef->update_default, dpcontext, exprs);
|
|
}
|
|
append_string_object(ret, "ON UPDATE %{on_update}s", "on_update", onupdate ? onupdate : "");
|
|
|
|
if (!is_alter || saw_autoincrement)
|
|
append_string_object(ret, "%{not_null}s", "not_null", saw_notnull ? "NOT NULL" : "");
|
|
|
|
/* GENERATED COLUMN EXPRESSION */
|
|
tmp_obj = new_objtree("GENERATED ALWAYS AS");
|
|
if (coldef->generatedCol == ATTRIBUTE_GENERATED_STORED) {
|
|
char *defstr;
|
|
|
|
defstr = RelationGetColumnDefault(relation, attrForm->attnum,
|
|
dpcontext, exprs);
|
|
append_string_object(tmp_obj, "(%{generation_expr}s) STORED",
|
|
"generation_expr", defstr);
|
|
} else {
|
|
append_not_present(tmp_obj, "(%{generation_expr}s) STORED");
|
|
}
|
|
append_object_object(ret, "%{generated_column}s", tmp_obj);
|
|
}
|
|
|
|
ReleaseSysCache(attrTup);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Deparse DefElems
|
|
*
|
|
* Verbose syntax
|
|
* %{label}s = %{value}L
|
|
*/
|
|
static ObjTree* deparse_DefElem(DefElem *elem, bool is_reset)
|
|
{
|
|
ObjTree *ret;
|
|
ObjTree *optname = new_objtree("");
|
|
|
|
if (elem->defnamespace != NULL)
|
|
append_string_object(optname, "%{schema}I.", "schema",
|
|
elem->defnamespace);
|
|
|
|
append_string_object(optname, "%{label}I", "label", elem->defname);
|
|
|
|
ret = new_objtree_VA("%{label}s", 1,
|
|
"label", ObjTypeObject, optname);
|
|
|
|
if (!is_reset) {
|
|
if (!elem->arg) {
|
|
append_string_object(ret, "= %{value}s", "value", defGetBoolean(elem) ? "TRUE" : "FALSE");
|
|
} else {
|
|
if (nodeTag(elem->arg) == T_String) {
|
|
append_string_object(ret, "= %{value}L", "value", defGetString(elem));
|
|
} else {
|
|
append_string_object(ret, "= %{value}s", "value", defGetString(elem));
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse the ON COMMIT ... clause for CREATE ... TEMPORARY ...
|
|
*
|
|
* Verbose syntax
|
|
* ON COMMIT %{on_commit_value}s
|
|
*/
|
|
static ObjTree* deparse_OnCommitClause(OnCommitAction option)
|
|
{
|
|
ObjTree *ret = new_objtree("ON COMMIT");
|
|
switch (option) {
|
|
case ONCOMMIT_DROP:
|
|
append_string_object(ret, "%{on_commit_value}s",
|
|
"on_commit_value", "DROP");
|
|
break;
|
|
|
|
case ONCOMMIT_DELETE_ROWS:
|
|
append_string_object(ret, "%{on_commit_value}s",
|
|
"on_commit_value", "DELETE ROWS");
|
|
break;
|
|
|
|
case ONCOMMIT_PRESERVE_ROWS:
|
|
append_string_object(ret, "%{on_commit_value}s",
|
|
"on_commit_value", "PRESERVE ROWS");
|
|
break;
|
|
|
|
case ONCOMMIT_NOOP:
|
|
ret = NULL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence CACHE option.
|
|
*
|
|
* Verbose syntax
|
|
* SET CACHE %{value}s
|
|
* OR
|
|
* CACHE %{value}
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Cache(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET CACHE %{value}s" : "CACHE %{value}s";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "cache",
|
|
"value", ObjTypeString, seqdata->cache_value);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence CYCLE option.
|
|
*
|
|
* Verbose syntax
|
|
* SET %{no}s CYCLE
|
|
* OR
|
|
* %{no}s CYCLE
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Cycle(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET %{no}s CYCLE" : "%{no}s CYCLE";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "cycle",
|
|
"no", ObjTypeString,
|
|
seqdata->is_cycled ? "" : "NO");
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence INCREMENT BY option.
|
|
*
|
|
* Verbose syntax
|
|
* SET INCREMENT BY %{value}s
|
|
* OR
|
|
* INCREMENT BY %{value}s
|
|
*/
|
|
static inline ObjElem* deparse_Seq_IncrementBy(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET INCREMENT BY %{value}s" : "INCREMENT BY %{value}s";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "seqincrement",
|
|
"value", ObjTypeString, seqdata->increment_by);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence MAXVALUE option.
|
|
*
|
|
* Verbose syntax
|
|
* SET MAXVALUE %{value}s
|
|
* OR
|
|
* MAXVALUE %{value}s
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Maxvalue(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET MAXVALUE %{value}s" : "MAXVALUE %{value}s";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "maxvalue",
|
|
"value", ObjTypeString, seqdata->max_value);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence MINVALUE option.
|
|
*
|
|
* Verbose syntax
|
|
* SET MINVALUE %{value}s
|
|
* OR
|
|
* MINVALUE %{value}s
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Minvalue(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET MINVALUE %{value}s" : "MINVALUE %{value}s";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "minvalue",
|
|
"value", ObjTypeString, seqdata->min_value);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence OWNED BY command.
|
|
*
|
|
* Verbose syntax
|
|
* OWNED BY %{owner}D
|
|
*/
|
|
static ObjElem* deparse_Seq_OwnedBy(Oid sequenceId)
|
|
{
|
|
ObjTree *ret = NULL;
|
|
Relation depRel;
|
|
SysScanDesc scan;
|
|
ScanKeyData keys[3];
|
|
HeapTuple tuple;
|
|
|
|
depRel = relation_open(DependRelationId, AccessShareLock);
|
|
ScanKeyInit(&keys[0],
|
|
Anum_pg_depend_classid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(RelationRelationId));
|
|
ScanKeyInit(&keys[1],
|
|
Anum_pg_depend_objid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(sequenceId));
|
|
ScanKeyInit(&keys[2],
|
|
Anum_pg_depend_objsubid,
|
|
BTEqualStrategyNumber, F_INT4EQ,
|
|
Int32GetDatum(0));
|
|
|
|
scan = systable_beginscan(depRel, DependDependerIndexId, true,
|
|
NULL, 3, keys);
|
|
|
|
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
|
|
Oid ownerId;
|
|
Form_pg_depend depform;
|
|
ObjTree *tmp_obj;
|
|
char *colname;
|
|
|
|
depform = (Form_pg_depend) GETSTRUCT(tuple);
|
|
|
|
/* Only consider AUTO dependencies on pg_class */
|
|
if (depform->deptype != DEPENDENCY_AUTO)
|
|
continue;
|
|
if (depform->refclassid != RelationRelationId)
|
|
continue;
|
|
if(depform->refobjsubid <= 0)
|
|
continue;
|
|
|
|
ownerId = depform->refobjid;
|
|
colname = get_attname(ownerId, depform->refobjsubid);
|
|
if (colname == NULL)
|
|
continue;
|
|
|
|
tmp_obj = new_objtree_for_qualname_id(RelationRelationId, ownerId);
|
|
append_string_object(tmp_obj, "attrname", "attrname", colname);
|
|
ret = new_objtree_VA("OWNED BY %{owner}D", 2,
|
|
"clause", ObjTypeString, "owned",
|
|
"owner", ObjTypeObject, tmp_obj);
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
relation_close(depRel, AccessShareLock);
|
|
|
|
/*
|
|
* If there's no owner column, emit an empty OWNED BY element, set up so
|
|
* that it won't print anything.
|
|
*/
|
|
if (!ret)
|
|
/* XXX this shouldn't happen */
|
|
ret = new_objtree_VA("OWNED BY %{owner}D", 3,
|
|
"clause", ObjTypeString, "owned",
|
|
"owner", ObjTypeNull,
|
|
"present", ObjTypeBool, false);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence ORDER option.
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Order(DefElem *elem)
|
|
{
|
|
ObjTree *ret;
|
|
|
|
ret = new_objtree_VA("%{order}s", 2,
|
|
"clause", ObjTypeString, "order",
|
|
"order", ObjTypeString, defGetBoolean(elem) ? "ORDER" : "NOORDER");
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
|
|
/*
|
|
* Deparse the sequence RESTART option.
|
|
*
|
|
* Verbose syntax
|
|
* RESTART %{value}s
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Restart(char *last_value)
|
|
{
|
|
ObjTree *ret;
|
|
ret = new_objtree_VA("RESTART %{value}s", 2,
|
|
"clause", ObjTypeString, "restart",
|
|
"value", ObjTypeString, last_value);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence AS option.
|
|
*
|
|
* Verbose syntax
|
|
* AS %{identity}D
|
|
*/
|
|
static inline ObjElem* deparse_Seq_As(DefElem *elem)
|
|
{
|
|
ObjTree *ret;
|
|
Type likeType;
|
|
Form_pg_type likeForm;
|
|
|
|
likeType = typenameType(NULL, defGetTypeName(elem), NULL);
|
|
likeForm = (Form_pg_type) GETSTRUCT(likeType);
|
|
|
|
ret = new_objtree_VA("AS %{identity}D", 1,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(likeForm->typnamespace,
|
|
NameStr(likeForm->typname)));
|
|
ReleaseSysCache(likeType);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
/*
|
|
* Deparse the sequence START WITH option.
|
|
*
|
|
* Verbose syntax
|
|
* SET START WITH %{value}s
|
|
* OR
|
|
* START WITH %{value}s
|
|
*/
|
|
static inline ObjElem* deparse_Seq_Startwith(sequence_values *seqdata, bool alter_table)
|
|
{
|
|
ObjTree *ret;
|
|
const char *fmt;
|
|
|
|
fmt = alter_table ? "SET START WITH %{value}s" : "START WITH %{value}s";
|
|
|
|
ret = new_objtree_VA(fmt, 2,
|
|
"clause", ObjTypeString, "start",
|
|
"value", ObjTypeString, seqdata->start_value);
|
|
|
|
return new_object_object(ret);
|
|
}
|
|
|
|
static bool istypestring(Oid typid)
|
|
{
|
|
switch (typid) {
|
|
case INT2OID:
|
|
case INT4OID:
|
|
case INT8OID:
|
|
case FLOAT4OID:
|
|
case FLOAT8OID:
|
|
case NUMERICOID:
|
|
/* Here we ignore infinity and NaN */
|
|
return false;
|
|
default:
|
|
/* All other types are regarded as string. */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Deparse the INHERITS relations.
|
|
*
|
|
* Given a table OID, return a schema-qualified table list representing
|
|
* the parent tables.
|
|
*/
|
|
static List* deparse_InhRelations(Oid objectId)
|
|
{
|
|
List *parents = NIL;
|
|
Relation inhRel;
|
|
SysScanDesc scan;
|
|
ScanKeyData key;
|
|
HeapTuple tuple;
|
|
|
|
inhRel = table_open(InheritsRelationId, RowExclusiveLock);
|
|
|
|
ScanKeyInit(&key,
|
|
Anum_pg_inherits_inhrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(objectId));
|
|
|
|
scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
|
|
true, NULL, 1, &key);
|
|
|
|
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
|
|
ObjTree *parent;
|
|
Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
|
|
|
|
parent = new_objtree_for_qualname_id(RelationRelationId,
|
|
formInh->inhparent);
|
|
parents = lappend(parents, new_object_object(parent));
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
table_close(inhRel, RowExclusiveLock);
|
|
|
|
return parents;
|
|
}
|
|
|
|
/*
|
|
* Subroutine for CREATE TABLE deparsing.
|
|
*
|
|
* Deal with all the table elements (columns and constraints).
|
|
*
|
|
* Note we ignore constraints in the parse node here; they are extracted from
|
|
* system catalogs instead.
|
|
*/
|
|
static List* deparse_TableElements(Relation relation, List *tableElements, List *dpcontext, bool composite)
|
|
{
|
|
List *elements = NIL;
|
|
ListCell *lc;
|
|
|
|
foreach(lc, tableElements) {
|
|
Node *elt = (Node *) lfirst(lc);
|
|
|
|
switch (nodeTag(elt)) {
|
|
case T_ColumnDef: {
|
|
ObjTree *tree;
|
|
tree = deparse_ColumnDef(relation, dpcontext,
|
|
composite, (ColumnDef *) elt,
|
|
false, NULL);
|
|
if (tree != NULL)
|
|
elements = lappend(elements, new_object_object(tree));
|
|
}
|
|
break;
|
|
case T_Constraint:
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid node type %d", nodeTag(elt));
|
|
}
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CreateSeqStmt.
|
|
*
|
|
* Given a sequence OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE %{persistence}s SEQUENCE %{identity}D
|
|
*/
|
|
static ObjTree* deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
Relation relation;
|
|
List *elems = NIL;
|
|
sequence_values *seqvalues;
|
|
CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parsetree;
|
|
|
|
if (createSeqStmt->is_autoinc)
|
|
return NULL;
|
|
|
|
seqvalues = get_sequence_values(objectId);
|
|
|
|
/* Definition elements */
|
|
elems = lappend(elems, deparse_Seq_Cache(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_Cycle(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_IncrementBy(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_Minvalue(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_Maxvalue(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_Startwith(seqvalues, false));
|
|
elems = lappend(elems, deparse_Seq_Restart(seqvalues->last_value));
|
|
|
|
/* We purposefully do not emit OWNED BY here */
|
|
|
|
relation = relation_open(objectId, AccessShareLock);
|
|
|
|
ret = new_objtree_VA("CREATE %{persistence}s %{large}s SEQUENCE %{identity}D %{definition: }s", 4,
|
|
"persistence", ObjTypeString, get_persistence_str(relation->rd_rel->relpersistence),
|
|
"large", ObjTypeString, seqvalues->large ? "LARGE" : "",
|
|
"identity", ObjTypeObject, new_objtree_for_qualname(relation->rd_rel->relnamespace,
|
|
RelationGetRelationName(relation)),
|
|
"definition", ObjTypeArray, elems);
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse an AlterSeqStmt
|
|
* Given a sequence OID and a parse tree that modified it, return an ObjTree
|
|
* representing the alter command
|
|
*
|
|
* Verbose syntax
|
|
* ALTER SEQUENCE %{identity}D %{definition: }s
|
|
*/
|
|
static ObjTree* deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
Relation relation;
|
|
List *elems = NIL;
|
|
ListCell *cell;
|
|
sequence_values *seqvalues;
|
|
AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parsetree;
|
|
|
|
if (alterSeqStmt->is_autoinc) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!alterSeqStmt->options) {
|
|
return NULL;
|
|
}
|
|
|
|
seqvalues = get_sequence_values(objectId);
|
|
|
|
foreach(cell, alterSeqStmt->options) {
|
|
DefElem *elem = (DefElem *) lfirst(cell);
|
|
ObjElem *newelm = NULL;
|
|
|
|
if (strcmp(elem->defname, "cache") == 0)
|
|
newelm = deparse_Seq_Cache(seqvalues, false);
|
|
else if (strcmp(elem->defname, "cycle") == 0)
|
|
newelm = deparse_Seq_Cycle(seqvalues, false);
|
|
else if (strcmp(elem->defname, "increment") == 0)
|
|
newelm = deparse_Seq_IncrementBy(seqvalues, false);
|
|
else if (strcmp(elem->defname, "minvalue") == 0)
|
|
newelm = deparse_Seq_Minvalue(seqvalues, false);
|
|
else if (strcmp(elem->defname, "maxvalue") == 0)
|
|
newelm = deparse_Seq_Maxvalue(seqvalues, false);
|
|
else if (strcmp(elem->defname, "start") == 0)
|
|
newelm = deparse_Seq_Startwith(seqvalues, false);
|
|
else if (strcmp(elem->defname, "restart") == 0)
|
|
newelm = deparse_Seq_Restart(seqvalues->last_value);
|
|
else if (strcmp(elem->defname, "owned_by") == 0)
|
|
newelm = deparse_Seq_OwnedBy(objectId);
|
|
else if (strcmp(elem->defname, "as") == 0)
|
|
newelm = deparse_Seq_As(elem);
|
|
else if (strcmp(elem->defname, "order") == 0)
|
|
newelm = deparse_Seq_Order(elem);
|
|
else
|
|
elog(WARNING, "unsupport sequence option %s for replication", elem->defname);
|
|
|
|
elems = lappend(elems, newelm);
|
|
}
|
|
|
|
relation = relation_open(objectId, AccessShareLock);
|
|
|
|
ret = new_objtree_VA("ALTER %{large}s SEQUENCE %{identity}D %{definition: }s", 3,
|
|
"large", ObjTypeString, seqvalues->large ? "LARGE" : "",
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(relation->rd_rel->relnamespace,
|
|
RelationGetRelationName(relation)),
|
|
"definition", ObjTypeArray, elems);
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* deparse_ViewStmt
|
|
* deparse a ViewStmt
|
|
*
|
|
* Given a view OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s
|
|
*/
|
|
static ObjTree* deparse_ViewStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ViewStmt *node = (ViewStmt *) parsetree;
|
|
ObjTree *ret;
|
|
Relation relation;
|
|
|
|
relation = relation_open(objectId, AccessShareLock);
|
|
|
|
ret = new_objtree_VA("CREATE %{or_replace}s %{persistence}s VIEW %{identity}D AS %{query}s", 4,
|
|
"or_replace", ObjTypeString,
|
|
node->replace ? "OR REPLACE" : "",
|
|
"persistence", ObjTypeString,
|
|
get_persistence_str(relation->rd_rel->relpersistence),
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(relation->rd_rel->relnamespace,
|
|
RelationGetRelationName(relation)),
|
|
"query", ObjTypeString,
|
|
pg_get_viewdef_string(objectId));
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a RenameStmt.
|
|
*/
|
|
static ObjTree* deparse_RenameStmt(ObjectAddress address, Node *parsetree)
|
|
{
|
|
RenameStmt *node = (RenameStmt *) parsetree;
|
|
ObjTree *ret;
|
|
Relation relation;
|
|
Oid schemaId;
|
|
|
|
if (node->is_modifycolumn) {
|
|
/* modify column in dbcompatibility B */
|
|
return NULL;
|
|
}
|
|
|
|
if (node->renameTableflag) {
|
|
/* rename table syntax in dbcompatibility B */
|
|
ListCell *cell = NULL;
|
|
List *renamelist = NIL;
|
|
foreach (cell, node->renameTargetList) {
|
|
RenameCell* renameInfo = (RenameCell*)lfirst(cell);
|
|
RangeVar *cur = NULL;
|
|
RangeVar *ori = NULL;
|
|
Oid nspoid = InvalidOid;
|
|
Oid tbloid = InvalidOid;
|
|
ObjTree *tmp_obj = NULL;
|
|
|
|
ori = renameInfo->original_name;
|
|
cur = renameInfo->modify_name;
|
|
|
|
if (!cur->schemaname || !ori->schemaname) {
|
|
continue;
|
|
}
|
|
|
|
nspoid = get_namespace_oid(cur->schemaname, false);
|
|
tbloid = get_relname_relid(cur->relname, nspoid);
|
|
if (!OidIsValid(tbloid)) {
|
|
elog(ERROR, "can not find the table %s.%s for deparse rename table",
|
|
cur->schemaname, cur->relname);
|
|
}
|
|
if (!relation_support_ddl_replication(tbloid, false)) {
|
|
continue;
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("%{ori}D TO %{modify}D", 2,
|
|
"ori", ObjTypeObject, new_objtree_for_qualname_rangevar(ori),
|
|
"modify", ObjTypeObject, new_objtree_for_qualname_rangevar(cur));
|
|
|
|
renamelist = lappend(renamelist, new_object_object(tmp_obj));
|
|
}
|
|
|
|
if (renamelist) {
|
|
ret = new_objtree_VA("RENAME TABLE %{renamelist:, }s", 1,
|
|
"renamelist", ObjTypeArray, renamelist);
|
|
return ret;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In an ALTER .. RENAME command, we don't have the original name of the
|
|
* object in system catalogs: since we inspect them after the command has
|
|
* executed, the old name is already gone. Therefore, we extract it from
|
|
* the parse node. Note we still extract the schema name from the catalog
|
|
* (it might not be present in the parse node); it cannot possibly have
|
|
* changed anyway.
|
|
*/
|
|
switch (node->renameType) {
|
|
case OBJECT_TABLE:
|
|
case OBJECT_INDEX:
|
|
case OBJECT_SEQUENCE:
|
|
case OBJECT_LARGE_SEQUENCE:
|
|
case OBJECT_VIEW:
|
|
case OBJECT_MATVIEW:
|
|
relation = relation_open(address.objectId, AccessShareLock);
|
|
schemaId = RelationGetNamespace(relation);
|
|
ret = new_objtree_VA("ALTER %{objtype}s %{if_exists}s %{identity}D RENAME TO %{newname}I", 4,
|
|
"objtype", ObjTypeString,
|
|
string_objtype(node->renameType, false),
|
|
"if_exists", ObjTypeString,
|
|
node->missing_ok ? "IF EXISTS" : "",
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(schemaId,
|
|
node->relation->relname),
|
|
"newname", ObjTypeString,
|
|
node->newname);
|
|
relation_close(relation, AccessShareLock);
|
|
break;
|
|
|
|
case OBJECT_ATTRIBUTE:
|
|
case OBJECT_COLUMN:
|
|
relation = relation_open(address.objectId, AccessShareLock);
|
|
schemaId = RelationGetNamespace(relation);
|
|
|
|
if (node->renameType == OBJECT_ATTRIBUTE) {
|
|
ret = new_objtree_VA("ALTER TYPE %{identity}D RENAME ATTRIBUTE %{colname}I", 2,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(schemaId,
|
|
node->relation->relname),
|
|
"colname", ObjTypeString, node->subname);
|
|
} else {
|
|
ret = new_objtree_VA("ALTER %{objtype}s", 1,
|
|
"objtype", ObjTypeString,
|
|
string_objtype(node->relationType, false));
|
|
|
|
/* Composite types do not support IF EXISTS */
|
|
if (node->renameType == OBJECT_COLUMN)
|
|
append_string_object(ret, "%{if_exists}s",
|
|
"if_exists",
|
|
node->missing_ok ? "IF EXISTS" : "");
|
|
|
|
append_object_object(ret, "%{identity}D",
|
|
new_objtree_for_qualname(schemaId,
|
|
node->relation->relname));
|
|
append_string_object(ret, "RENAME COLUMN %{colname}I",
|
|
"colname", node->subname);
|
|
}
|
|
|
|
append_string_object(ret, "TO %{newname}I", "newname", node->newname);
|
|
|
|
if (node->renameType == OBJECT_ATTRIBUTE)
|
|
append_object_object(ret, "%{cascade}s",
|
|
new_objtree_VA("CASCADE", 1,
|
|
"present", ObjTypeBool,
|
|
node->behavior == DROP_CASCADE));
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
break;
|
|
|
|
case OBJECT_SCHEMA:
|
|
ret = new_objtree_VA("ALTER SCHEMA %{identity}I RENAME TO %{newname}I", 2,
|
|
"identity", ObjTypeString, node->subname,
|
|
"newname", ObjTypeString, node->newname);
|
|
break;
|
|
case OBJECT_TABCONSTRAINT: {
|
|
HeapTuple constrtup;
|
|
Form_pg_constraint constform;
|
|
|
|
constrtup = SearchSysCache1(CONSTROID,
|
|
ObjectIdGetDatum(address.objectId));
|
|
if (!HeapTupleIsValid(constrtup))
|
|
elog(ERROR, "cache lookup failed for constraint with OID %u",
|
|
address.objectId);
|
|
constform = (Form_pg_constraint) GETSTRUCT(constrtup);
|
|
|
|
ret = new_objtree_VA("ALTER TABLE %{identity}D RENAME CONSTRAINT %{oldname}I TO %{newname}I", 3,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname_id(RelationRelationId,
|
|
constform->conrelid),
|
|
"oldname", ObjTypeString, node->subname,
|
|
"newname", ObjTypeString, node->newname);
|
|
ReleaseSysCache(constrtup);
|
|
}
|
|
break;
|
|
case OBJECT_TYPE: {
|
|
HeapTuple typtup;
|
|
Form_pg_type typform;
|
|
Oid nspid;
|
|
|
|
List* names = node->object;
|
|
char *typeName;
|
|
char *schemaname;
|
|
char *pkgName;
|
|
DeconstructQualifiedName(names, &schemaname, &typeName, &pkgName);
|
|
|
|
typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(address.objectId));
|
|
if (!HeapTupleIsValid(typtup))
|
|
elog(ERROR, "cache lookup failed for type with OID %u", address.objectId);
|
|
typform = (Form_pg_type) GETSTRUCT(typtup);
|
|
nspid = typform->typnamespace;
|
|
|
|
ret = new_objtree_VA("ALTER TYPE %{identity}D RENAME TO %{newname}I", 2,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(nspid,
|
|
typeName),
|
|
"newname", ObjTypeString, node->newname);
|
|
|
|
ReleaseSysCache(typtup);
|
|
}
|
|
break;
|
|
default:
|
|
elog(WARNING, "unsupported RenameStmt object type %d", node->renameType);
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CommentStmt when it pertains to a constraint.
|
|
*
|
|
* Verbose syntax
|
|
* COMMENT ON CONSTRAINT %{identity}s ON [DOMAIN] %{parentobj}s IS %{comment}s
|
|
*/
|
|
static ObjTree* deparse_CommentOnConstraintSmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CommentStmt *node = (CommentStmt *) parsetree;
|
|
ObjTree *ret;
|
|
HeapTuple constrTup;
|
|
Form_pg_constraint constrForm;
|
|
ObjectAddress addr;
|
|
|
|
Assert(node->objtype == OBJECT_TABCONSTRAINT || node->objtype == OBJECT_DOMCONSTRAINT);
|
|
|
|
constrTup = SearchSysCache1(CONSTROID, objectId);
|
|
if (!HeapTupleIsValid(constrTup))
|
|
elog(ERROR, "cache lookup failed for constraint with OID %u", objectId);
|
|
constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
|
|
|
|
if (OidIsValid(constrForm->conrelid))
|
|
ObjectAddressSet(addr, RelationRelationId, constrForm->conrelid);
|
|
else
|
|
ObjectAddressSet(addr, TypeRelationId, constrForm->contypid);
|
|
|
|
ret = new_objtree_VA("COMMENT ON CONSTRAINT %{identity}s ON %{domain}s %{parentobj}s", 3,
|
|
"identity", ObjTypeString, pstrdup(NameStr(constrForm->conname)),
|
|
"domain", ObjTypeString,
|
|
(node->objtype == OBJECT_DOMCONSTRAINT) ? "DOMAIN" : "",
|
|
"parentobj", ObjTypeString,
|
|
getObjectIdentity(&addr));
|
|
|
|
/* Add the comment clause */
|
|
append_literal_or_null(ret, "IS %{comment}s", node->comment);
|
|
|
|
ReleaseSysCache(constrTup);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse an CommentStmt (COMMENT ON ...).
|
|
*
|
|
* Given the object address and the parse tree that created it, return an
|
|
* ObjTree representing the comment command.
|
|
*
|
|
* Verbose syntax
|
|
* COMMENT ON %{objtype}s %{identity}s IS %{comment}s
|
|
*/
|
|
static ObjTree* deparse_CommentStmt(ObjectAddress address, Node *parsetree)
|
|
{
|
|
CommentStmt *node = (CommentStmt *) parsetree;
|
|
ObjTree *ret;
|
|
char *identity;
|
|
|
|
/* Comment on subscription is not supported */
|
|
if (node->objtype == OBJECT_SUBSCRIPTION)
|
|
return NULL;
|
|
|
|
/*
|
|
* Constraints are sufficiently different that it is easier to handle them
|
|
* separately.
|
|
*/
|
|
if (node->objtype == OBJECT_DOMCONSTRAINT ||
|
|
node->objtype == OBJECT_TABCONSTRAINT) {
|
|
Assert(address.classId == ConstraintRelationId);
|
|
return deparse_CommentOnConstraintSmt(address.objectId, parsetree);
|
|
}
|
|
|
|
ret = new_objtree_VA("COMMENT ON %{objtype}s", 1,
|
|
"objtype", ObjTypeString,
|
|
(char *) string_objtype(node->objtype, false));
|
|
|
|
/*
|
|
* Add the object identity clause. For zero argument aggregates we need
|
|
* to add the (*) bit; in all other cases we can just use
|
|
* getObjectIdentity.
|
|
*
|
|
* XXX shouldn't we instead fix the object identities for zero-argument
|
|
* aggregates?
|
|
*/
|
|
if (node->objtype == OBJECT_AGGREGATE) {
|
|
HeapTuple procTup;
|
|
Form_pg_proc procForm;
|
|
|
|
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(address.objectId));
|
|
if (!HeapTupleIsValid(procTup))
|
|
elog(ERROR, "cache lookup failed for procedure with OID %u",
|
|
address.objectId);
|
|
procForm = (Form_pg_proc) GETSTRUCT(procTup);
|
|
if (procForm->pronargs == 0)
|
|
identity = psprintf("%s(*)",
|
|
quote_qualified_identifier(get_namespace_name(procForm->pronamespace),
|
|
NameStr(procForm->proname)));
|
|
else
|
|
identity = getObjectIdentity(&address);
|
|
ReleaseSysCache(procTup);
|
|
} else {
|
|
identity = getObjectIdentity(&address);
|
|
}
|
|
|
|
append_string_object(ret, "%{identity}s", "identity", identity);
|
|
|
|
/* Add the comment clause; can be either NULL or a quoted literal. */
|
|
append_literal_or_null(ret, "IS %{comment}s", node->comment);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CompositeTypeStmt (CREATE TYPE AS)
|
|
*
|
|
* Given a Composite type OID and the parse tree that created it, return an
|
|
* ObjTree representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE TYPE %{identity}D AS (%{columns:, }s)
|
|
*/
|
|
static ObjTree* deparse_CompositeTypeStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CompositeTypeStmt *node = (CompositeTypeStmt *) parsetree;
|
|
HeapTuple typtup;
|
|
Form_pg_type typform;
|
|
Relation typerel;
|
|
List *dpcontext;
|
|
List *tableelts = NIL;
|
|
|
|
/* Find the pg_type entry and open the corresponding relation */
|
|
typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
|
|
if (!HeapTupleIsValid(typtup))
|
|
elog(ERROR, "cache lookup failed for type with OID %u", objectId);
|
|
|
|
typform = (Form_pg_type) GETSTRUCT(typtup);
|
|
typerel = relation_open(typform->typrelid, AccessShareLock);
|
|
|
|
dpcontext = deparse_context_for(RelationGetRelationName(typerel),
|
|
RelationGetRelid(typerel));
|
|
|
|
tableelts = deparse_TableElements(typerel, node->coldeflist, dpcontext,
|
|
true); /* composite type */
|
|
|
|
table_close(typerel, AccessShareLock);
|
|
ReleaseSysCache(typtup);
|
|
|
|
return new_objtree_VA("CREATE TYPE %{identity}D AS (%{columns:, }s)", 2,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname_id(TypeRelationId, objectId),
|
|
"columns", ObjTypeArray, tableelts);
|
|
}
|
|
|
|
/*
|
|
* Deparse an IndexStmt.
|
|
*
|
|
* Given an index OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* If the index corresponds to a constraint, NULL is returned.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE %{unique}s INDEX %{concurrently}s %{if_not_exists}s %{name}I ON
|
|
* %{table}D USING %{index_am}s %{definition}s %{with}s %{tablespace}s
|
|
* %{where_clause}s
|
|
*/
|
|
static ObjTree* deparse_IndexStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
IndexStmt *node = (IndexStmt *) parsetree;
|
|
ObjTree *ret;
|
|
Relation idxrel;
|
|
Relation heaprel;
|
|
char *index_am;
|
|
char *definition;
|
|
char *reloptions;
|
|
char *tablespace;
|
|
char *whereClause;
|
|
bool invisible;
|
|
|
|
if (node->primary || node->isconstraint)
|
|
{
|
|
/*
|
|
* Indexes for PRIMARY KEY and other constraints are output
|
|
* separately; return empty here.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
idxrel = relation_open(objectId, AccessShareLock);
|
|
heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
|
|
|
|
pg_get_indexdef_detailed(objectId, node->isGlobal,
|
|
&index_am, &definition, &reloptions,
|
|
&tablespace, &whereClause, &invisible);
|
|
|
|
ret = new_objtree_VA(
|
|
"CREATE %{unique}s INDEX %{concurrently}s %{name}I ON %{table}D USING %{index_am}s %{definition}s", 6,
|
|
"unique", ObjTypeString, node->unique ? "UNIQUE" : "",
|
|
"concurrently", ObjTypeString, node->concurrent ? "CONCURRENTLY" : "",
|
|
"name", ObjTypeString, RelationGetRelationName(idxrel),
|
|
"table", ObjTypeObject,
|
|
new_objtree_for_qualname(heaprel->rd_rel->relnamespace, RelationGetRelationName(heaprel)),
|
|
"index_am", ObjTypeString, index_am,
|
|
"definition", ObjTypeString, definition);
|
|
|
|
/* reloptions */
|
|
if (reloptions)
|
|
append_string_object(ret, "WITH (%{opts}s)", "opts", reloptions);
|
|
|
|
/* tablespace */
|
|
if (tablespace)
|
|
append_string_object(ret, "TABLESPACE %{tablespace}s", "tablespace", tablespace);
|
|
|
|
if (invisible)
|
|
append_format_string(ret, "INVISIBLE");
|
|
|
|
/* WHERE clause */
|
|
|
|
if (whereClause)
|
|
append_string_object(ret, "WHERE %{where}s", "where", whereClause);
|
|
|
|
relation_close(idxrel, AccessShareLock);
|
|
relation_close(heaprel, AccessShareLock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CreateStmt (CREATE TABLE).
|
|
*
|
|
* Given a table OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D [OF
|
|
* %{of_type}T | PARTITION OF %{parent_identity}D] %{table_elements}s
|
|
* %{inherits}s %{partition_by}s %{access_method}s %{with_clause}s
|
|
* %{on_commit}s %{tablespace}s
|
|
*/
|
|
static ObjTree* deparse_CreateStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CreateStmt *node = (CreateStmt *) parsetree;
|
|
Relation relation = relation_open(objectId, AccessShareLock);
|
|
List *dpcontext;
|
|
ObjTree *ret;
|
|
ObjTree *tmp_obj;
|
|
List *list = NIL;
|
|
ListCell *cell;
|
|
|
|
ret = new_objtree_VA("CREATE %{persistence}s TABLE %{if_not_exists}s %{identity}D", 3,
|
|
"persistence", ObjTypeString, get_persistence_str(relation->rd_rel->relpersistence),
|
|
"if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
|
|
"identity", ObjTypeObject, new_objtree_for_qualname(relation->rd_rel->relnamespace,
|
|
RelationGetRelationName(relation)));
|
|
|
|
dpcontext = deparse_context_for(RelationGetRelationName(relation),
|
|
objectId);
|
|
|
|
if (node->ofTypename) {
|
|
tmp_obj = new_objtree_for_type(relation->rd_rel->reloftype, -1);
|
|
append_object_object(ret, "OF %{of_type}T", tmp_obj);
|
|
} else {
|
|
List *tableelts = NIL;
|
|
|
|
/*
|
|
* There is no need to process LIKE clauses separately; they have
|
|
* already been transformed into columns and constraints.
|
|
*/
|
|
|
|
/*
|
|
* Process table elements: column definitions and constraints. Only
|
|
* the column definitions are obtained from the parse node itself. To
|
|
* get constraints we rely on pg_constraint, because the parse node
|
|
* might be missing some things such as the name of the constraints.
|
|
*/
|
|
tableelts = deparse_TableElements(relation, node->tableElts, dpcontext,
|
|
false); /* not composite */
|
|
tableelts = obtainConstraints(tableelts, objectId);
|
|
|
|
tmp_obj = new_objtree("");
|
|
if (tableelts)
|
|
append_array_object(tmp_obj, "(%{elements:, }s)", tableelts);
|
|
else
|
|
append_format_string(tmp_obj, "()");
|
|
|
|
append_object_object(ret, "%{table_elements}s", tmp_obj);
|
|
|
|
/*
|
|
* Add inheritance specification. We cannot simply scan the list of
|
|
* parents from the parser node, because that may lack the actual
|
|
* qualified names of the parent relations. Rather than trying to
|
|
* re-resolve them from the information in the parse node, it seems
|
|
* more accurate and convenient to grab it from pg_inherits.
|
|
*/
|
|
tmp_obj = new_objtree("INHERITS");
|
|
if (node->inhRelations != NIL) {
|
|
append_array_object(tmp_obj, "(%{parents:, }D)", deparse_InhRelations(objectId));
|
|
} else {
|
|
append_not_present(tmp_obj, "(%{parents:, }D)");
|
|
}
|
|
append_object_object(ret, "%{inherits}s", tmp_obj);
|
|
}
|
|
|
|
/* AUTO_INCREMENT */
|
|
if (node->autoIncStart) {
|
|
DefElem *defel = (DefElem*)node->autoIncStart;
|
|
if (IsA(defel->arg, Integer)) {
|
|
append_int_object(ret, "AUTO_INCREMENT = %{autoincstart}n", (int32)intVal(defel->arg));
|
|
} else {
|
|
append_string_object(ret, "AUTO_INCREMENT = %{autoincstart}s", "autoincstart", strVal(defel->arg));
|
|
}
|
|
}
|
|
|
|
/* WITH clause */
|
|
foreach (cell, node->options) {
|
|
ObjTree *tmp_obj2;
|
|
DefElem *opt = (DefElem *)lfirst(cell);
|
|
|
|
/* filter out some options which we won't set in subscription */
|
|
if (!pg_strcasecmp(opt->defname, "parallel_workers")) {
|
|
continue;
|
|
}
|
|
|
|
tmp_obj2 = deparse_DefElem(opt, false);
|
|
if (tmp_obj2)
|
|
list = lappend(list, new_object_object(tmp_obj2));
|
|
}
|
|
if (list)
|
|
append_array_object(ret, "WITH (%{with:, }s)", list);
|
|
|
|
if (node->oncommit != ONCOMMIT_NOOP)
|
|
append_object_object(ret, "%{on_commit}s",
|
|
deparse_OnCommitClause(node->oncommit));
|
|
|
|
if (node->tablespacename)
|
|
append_string_object(ret, "TABLESPACE %{tablespace}I", "tablespace",
|
|
node->tablespacename);
|
|
|
|
/* opt_table_options (COMMENT = XX) */
|
|
List *table_options = NIL;
|
|
foreach(cell, node->tableOptions) {
|
|
if (IsA(lfirst(cell), CommentStmt)) {
|
|
CommentStmt *commentStmt = (CommentStmt *)lfirst(cell);
|
|
if (commentStmt->comment) {
|
|
/* order in reverse */
|
|
ObjTree *option_obj = new_objtree("");
|
|
append_string_object(option_obj, "COMMENT=%{comment}L", "comment", commentStmt->comment);
|
|
table_options = lcons(new_object_object(option_obj), table_options);
|
|
}
|
|
}
|
|
}
|
|
if (table_options)
|
|
append_array_object(ret, "%{options:, }s", table_options);
|
|
|
|
/* opt_table_partitioning_clause */
|
|
if (node->partTableState && relation->rd_rel->parttype != PARTTYPE_NON_PARTITIONED_RELATION) {
|
|
append_string_object(ret, "%{partition_by}s", "partition_by", pg_get_partkeydef_string(relation));
|
|
}
|
|
|
|
relation_close(relation, AccessShareLock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*
|
|
declare
|
|
null_cnt int;
|
|
start_num int;
|
|
sql text;
|
|
begin
|
|
select count(*) from %{identity}D where %{colname}I is null into null_cnt;
|
|
start_num := intmax - null_cnt;
|
|
sql := 'create large sequence %{seqname}D start with ' || start_num;
|
|
execute sql;
|
|
update %{identity}D set %{colname}I=pg_catalog.nextval('%{seqname}D') WEHRE %{colname}I IS NULL;
|
|
DROP large SEQUENCE %{seqname}D;
|
|
end;
|
|
/
|
|
*/
|
|
|
|
static char ADAPT_SUBSCTIPTION_AUTOINCREMENT_FMT[] =
|
|
"DECLARE\n"
|
|
"null_cnt pg_catalog.%{typname}s;\n"
|
|
"start_num pg_catalog.%{typname}s;\n"
|
|
"sql pg_catalog.text;\n"
|
|
"BEGIN\n"
|
|
"SELECT COUNT(*) FROM %{identity}D WHERE %{colname}I IS NULL INTO null_cnt;\n"
|
|
"SELECT pg_catalog.min(%{colname}I) - 1 - null_cnt FROM %{identity}D INTO start_num;\n"
|
|
"sql := 'CREATE LARGE SEQUENCE %{seqname}D START WITH ' || start_num || ' MINVALUE ' || start_num;\n"
|
|
"EXECUTE sql;\n"
|
|
"UPDATE %{identity}D SET %{colname}I = pg_catalog.nextval('%{seqname}D') WHERE %{colname}I IS NULL;\n"
|
|
"DROP LARGE SEQUENCE %{seqname}D;\n"
|
|
"END;\n";
|
|
|
|
static ObjTree* adapt_subscription_autoincrement_null_value(Relation rel, ColumnDef *coldef, Oid typid)
|
|
{
|
|
ObjTree *tmp_obj = NULL;
|
|
char *maxvalue = NULL;
|
|
char *seqname = NULL;
|
|
char *typname = NULL;
|
|
sequence_values *seqvalues = get_sequence_values(RelAutoIncSeqOid(rel));
|
|
StringInfoData string_buf;
|
|
|
|
initStringInfo(&string_buf);
|
|
appendStringInfo(&string_buf, "ddl_replication_%s", seqvalues->sequence_name);
|
|
seqname = pstrdup(string_buf.data);
|
|
|
|
resetStringInfo(&string_buf);
|
|
|
|
switch (typid) {
|
|
case BOOLOID:
|
|
maxvalue = pstrdup("1");
|
|
typname = pstrdup("bool");
|
|
break;
|
|
case INT1OID:
|
|
appendStringInfo(&string_buf, "%u", UCHAR_MAX);
|
|
maxvalue = pstrdup(string_buf.data);
|
|
typname = pstrdup("int1");
|
|
break;
|
|
case INT2OID:
|
|
appendStringInfo(&string_buf, "%d", SHRT_MAX);
|
|
maxvalue = pstrdup(string_buf.data);
|
|
typname = pstrdup("int2");
|
|
break;
|
|
case INT4OID:
|
|
appendStringInfo(&string_buf, "%d", INT_MAX);
|
|
maxvalue = pstrdup(string_buf.data);
|
|
typname = pstrdup("int4");
|
|
break;
|
|
case INT8OID :
|
|
case FLOAT4OID :
|
|
case FLOAT8OID : {
|
|
char buf[MAXINT8LEN + 1];
|
|
pg_lltoa(PG_INT64_MAX, buf);
|
|
maxvalue = pstrdup(buf);
|
|
/* just use int8 for create sequence */
|
|
typname = pstrdup("int8");
|
|
}
|
|
break;
|
|
case INT16OID: {
|
|
const int MAXINT16LEN = 45;
|
|
char buf[MAXINT16LEN + 1];
|
|
pg_i128toa(PG_INT128_MAX, buf, MAXINT16LEN + 1);
|
|
maxvalue = pstrdup(buf);
|
|
typname = pstrdup("int16");
|
|
}
|
|
break;
|
|
default : {
|
|
appendStringInfo(&string_buf, "%d", INT_MAX);
|
|
maxvalue = pstrdup(string_buf.data);
|
|
typname = pstrdup("int4");
|
|
break;
|
|
}
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA(ADAPT_SUBSCTIPTION_AUTOINCREMENT_FMT, 4,
|
|
"seqname", ObjTypeObject, new_objtree_for_qualname(rel->rd_rel->relnamespace, seqname),
|
|
"identity", ObjTypeObject, new_objtree_for_qualname(rel->rd_rel->relnamespace, RelationGetRelationName(rel)),
|
|
"colname", ObjTypeString, coldef->colname,
|
|
"typname", ObjTypeString, typname);
|
|
|
|
FreeStringInfo(&string_buf);
|
|
return tmp_obj;
|
|
}
|
|
|
|
static List* deparse_AlterRelation_add_column_default(CollectedCommand *cmd)
|
|
{
|
|
ObjTree *ret = NULL;
|
|
ObjTree *tmp_obj = NULL;
|
|
List *dpcontext;
|
|
Relation rel;
|
|
List *subcmds = NIL;
|
|
ListCell *cell;
|
|
List *exprs = NIL;
|
|
Oid relId = cmd->d.alterTable.objectId;
|
|
AlterTableStmt *stmt = NULL;
|
|
bool isonly = false;
|
|
bool isrewrite = false;
|
|
|
|
List *tree_list = NIL;
|
|
List *not_null_list = NIL;
|
|
List *constraint_list = NIL;
|
|
|
|
isrewrite = cmd->d.alterTable.rewrite;
|
|
|
|
rel = relation_open(relId, AccessShareLock);
|
|
dpcontext = deparse_context_for(RelationGetRelationName(rel),
|
|
relId);
|
|
stmt = (AlterTableStmt *) cmd->parsetree;
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION ||
|
|
rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) {
|
|
relation_close(rel, AccessShareLock);
|
|
return NIL;
|
|
}
|
|
|
|
if (stmt->relation && stmt->relation->inhOpt == INH_NO) {
|
|
isonly = true;
|
|
}
|
|
|
|
foreach(cell, cmd->d.alterTable.subcmds) {
|
|
CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
|
|
AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
|
|
|
|
if (subcmd->recursing)
|
|
continue;
|
|
|
|
switch (subcmd->subtype) {
|
|
case AT_AddColumn:
|
|
case AT_AddColumnRecurse: {
|
|
ColumnDef *coldef = (ColumnDef*)subcmd->def;
|
|
HeapTuple attrTup;
|
|
Form_pg_attribute attrForm;
|
|
Oid typid;
|
|
int32 typmod;
|
|
Oid typcollation;
|
|
|
|
/* do nothing */
|
|
if (coldef->generatedCol == ATTRIBUTE_GENERATED_STORED) {
|
|
break;
|
|
}
|
|
|
|
attrTup = SearchSysCacheAttName(relId, coldef->colname);
|
|
if (!HeapTupleIsValid(attrTup))
|
|
elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
|
|
coldef->colname, relId);
|
|
attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
|
|
if (!attrForm->atthasdef) {
|
|
ReleaseSysCache(attrTup);
|
|
break;
|
|
}
|
|
|
|
get_atttypetypmodcoll(relId, attrForm->attnum, &typid, &typmod, &typcollation);
|
|
|
|
/* for auto_increment, construct the modify column clause after rewrite */
|
|
if (coldef->raw_default && IsA(coldef->raw_default, AutoIncrement)) {
|
|
if (attrForm->attnotnull) {
|
|
tmp_obj = adapt_subscription_autoincrement_null_value(rel, coldef, typid);
|
|
if (tmp_obj) {
|
|
tree_list = lappend(tree_list, tmp_obj);
|
|
}
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("MODIFY COLUMN %{colname}I %{coltype}T AUTO_INCREMENT", 2,
|
|
"colname", ObjTypeString, coldef->colname,
|
|
"coltype", ObjTypeObject, new_objtree_for_type(typid, typmod));
|
|
if (!coldef->is_not_null) {
|
|
append_format_string(tmp_obj, "NULL");
|
|
}
|
|
constraint_list = lappend(constraint_list, new_object_object(tmp_obj));
|
|
ReleaseSysCache(attrTup);
|
|
|
|
break;
|
|
}
|
|
|
|
if (coldef->is_not_null) {
|
|
tmp_obj = new_objtree_VA(
|
|
"ALTER TABLE %{only}s %{identity}D ALTER COLUMN %{name}I SET NOT NULL", 3,
|
|
"only", ObjTypeString, isonly ? "ONLY" : "",
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(rel->rd_rel->relnamespace,
|
|
RelationGetRelationName(rel)),
|
|
"name", ObjTypeString, coldef->colname);
|
|
not_null_list = lappend(not_null_list, tmp_obj);
|
|
}
|
|
|
|
char *defstr = NULL;
|
|
char *initdefval = NULL;
|
|
|
|
if (coldef->initdefval) {
|
|
StringInfoData defvalbuf;
|
|
initStringInfo(&defvalbuf);
|
|
appendStringInfo(&defvalbuf, "\'%s\'", coldef->initdefval);
|
|
initdefval = pstrdup(defvalbuf.data);
|
|
}
|
|
defstr = RelationGetColumnDefault(rel, attrForm->attnum, dpcontext, &exprs);
|
|
if (!coldef->initdefval) {
|
|
check_alter_table_replident(rel);
|
|
ObjTree *update_ret = new_objtree_VA(
|
|
"UPDATE %{identity}D SET %{name}I = %{default}s WHERE %{name}I IS NULL", 3,
|
|
"identity", ObjTypeObject, new_objtree_for_qualname(rel->rd_rel->relnamespace,
|
|
RelationGetRelationName(rel)),
|
|
"name", ObjTypeString, coldef->colname,
|
|
"default", ObjTypeString, initdefval ? initdefval : defstr);
|
|
tree_list = lappend(tree_list, update_ret);
|
|
}
|
|
|
|
ret = new_objtree_VA(
|
|
"ALTER TABLE %{only}s %{identity}D", 2, "only", ObjTypeString, isonly ? "ONLY" : "", "identity",
|
|
ObjTypeObject,
|
|
new_objtree_for_qualname(rel->rd_rel->relnamespace, RelationGetRelationName(rel)));
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{name}I SET DEFAULT %{default}s", 2, "name", ObjTypeString,
|
|
coldef->colname, "default", ObjTypeString, defstr);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
append_array_object(ret, "%{subcmds:, }s", subcmds);
|
|
tree_list = lappend(tree_list, ret);
|
|
|
|
ReleaseSysCache(attrTup);
|
|
}
|
|
break;
|
|
case AT_ModifyColumn: {
|
|
/* handle auto_increment attribute */
|
|
AttrNumber attnum;
|
|
Oid typid;
|
|
int32 typmod;
|
|
Oid typcollation;
|
|
ColumnDef *coldef = (ColumnDef *) subcmd->def;
|
|
ListCell *lc2 = NULL;
|
|
|
|
HeapTuple attrTup;
|
|
Form_pg_attribute attrForm;
|
|
|
|
bool saw_autoincrement = false;
|
|
bool saw_null = false;
|
|
attrTup = SearchSysCacheAttName(relId, coldef->colname);
|
|
if (!HeapTupleIsValid(attrTup))
|
|
elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
|
|
coldef->colname, relId);
|
|
attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
|
|
if (!attrForm->atthasdef) {
|
|
ReleaseSysCache(attrTup);
|
|
break;
|
|
}
|
|
attnum = attrForm->attnum;
|
|
get_atttypetypmodcoll(RelationGetRelid(rel), attnum, &typid, &typmod, &typcollation);
|
|
|
|
foreach(lc2, coldef->constraints) {
|
|
Constraint *constr = (Constraint *) lfirst(lc2);
|
|
|
|
if (constr->contype == CONSTR_AUTO_INCREMENT) {
|
|
saw_autoincrement = true;
|
|
} else if (constr->contype == CONSTR_NULL) {
|
|
saw_null = true;
|
|
}
|
|
}
|
|
|
|
if (!saw_autoincrement) {
|
|
break;
|
|
}
|
|
|
|
if (attrForm->attnotnull) {
|
|
tmp_obj = adapt_subscription_autoincrement_null_value(rel, coldef, typid);
|
|
if (tmp_obj)
|
|
tree_list = lappend(tree_list, tmp_obj);
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("MODIFY COLUMN %{colname}I %{coltype}T AUTO_INCREMENT", 2,
|
|
"colname", ObjTypeString, coldef->colname,
|
|
"coltype", ObjTypeObject, new_objtree_for_type(typid, typmod));
|
|
if (saw_null) {
|
|
append_format_string(tmp_obj, "NULL");
|
|
}
|
|
constraint_list = lappend(constraint_list, new_object_object(tmp_obj));
|
|
ReleaseSysCache(attrTup);
|
|
}
|
|
break;
|
|
case AT_AddIndex: {
|
|
Oid idxOid = sub->address.objectId;
|
|
IndexStmt *istmt;
|
|
Relation idx;
|
|
const char *idxname;
|
|
Oid constrOid;
|
|
istmt = (IndexStmt *) subcmd->def;
|
|
if (!istmt->isconstraint || !isrewrite)
|
|
break;
|
|
|
|
idx = relation_open(idxOid, AccessShareLock);
|
|
idxname = RelationGetRelationName(idx);
|
|
|
|
constrOid = get_relation_constraint_oid(cmd->d.alterTable.objectId, idxname, false);
|
|
|
|
tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
|
|
"type", ObjTypeString, "add constraint",
|
|
"name", ObjTypeString, idxname,
|
|
"definition", ObjTypeString,
|
|
pg_get_constraintdef_part_string(constrOid));
|
|
constraint_list = lappend(constraint_list, new_object_object(tmp_obj));
|
|
|
|
relation_close(idx, AccessShareLock);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (not_null_list) {
|
|
tree_list = list_concat(tree_list, not_null_list);
|
|
}
|
|
|
|
if (constraint_list) {
|
|
tmp_obj = new_objtree_VA("ALTER TABLE %{only}s %{identity}D", 2,
|
|
"only", ObjTypeString, isonly ? "ONLY" : "",
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(rel->rd_rel->relnamespace,
|
|
RelationGetRelationName(rel)));
|
|
append_array_object(tmp_obj, "%{subcmds:, }s", constraint_list);
|
|
tree_list = lappend(tree_list, tmp_obj);
|
|
}
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
return tree_list;
|
|
}
|
|
|
|
List* deparse_altertable_end(CollectedCommand *cmd)
|
|
{
|
|
OverrideSearchPath *overridePath;
|
|
MemoryContext oldcxt;
|
|
MemoryContext tmpcxt;
|
|
ObjTree *tree;
|
|
|
|
List *command_list = NIL;
|
|
List *tree_list = NIL;
|
|
List *res = NIL;
|
|
StringInfoData str;
|
|
|
|
if (cmd->type != SCT_AlterTable || !IsA(cmd->parsetree, AlterTableStmt)) {
|
|
return NIL;
|
|
}
|
|
|
|
initStringInfo(&str);
|
|
tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
|
|
"deparse ctx",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
oldcxt = MemoryContextSwitchTo(tmpcxt);
|
|
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
|
overridePath->schemas = NIL;
|
|
overridePath->addCatalog = false;
|
|
overridePath->addTemp = true;
|
|
PushOverrideSearchPath(overridePath);
|
|
|
|
tree_list = deparse_AlterRelation_add_column_default(cmd);
|
|
ListCell* lc = NULL;
|
|
foreach(lc, tree_list) {
|
|
tree = (ObjTree*)lfirst(lc);
|
|
Jsonb *jsonb;
|
|
char *command = NULL;
|
|
jsonb = objtree_to_jsonb(tree, NULL);
|
|
command = JsonbToCString(&str, VARDATA(jsonb), JSONB_ESTIMATED_LEN);
|
|
|
|
command_list = lappend(command_list, MemoryContextStrdup(oldcxt, command));
|
|
resetStringInfo(&str);
|
|
}
|
|
|
|
PopOverrideSearchPath();
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
res = list_copy(command_list);
|
|
MemoryContextDelete(tmpcxt);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handle deparsing of DROP commands.
|
|
*
|
|
* Verbose syntax
|
|
* DROP %s IF EXISTS %%{objidentity}s %{cascade}s
|
|
*/
|
|
char* deparse_drop_command(const char *objidentity, const char *objecttype,
|
|
Node *parsetree)
|
|
{
|
|
DropStmt *node = (DropStmt*)parsetree;
|
|
StringInfoData str;
|
|
char *command;
|
|
char *identity = (char *) objidentity;
|
|
ObjTree *stmt;
|
|
Jsonb *jsonb;
|
|
bool concurrent = false;
|
|
|
|
initStringInfo(&str);
|
|
|
|
if ((!strcmp(objecttype, "index") && node->concurrent))
|
|
concurrent = true;
|
|
|
|
stmt = new_objtree_VA("DROP %{objtype}s %{concurrently}s %{if_exists}s %{objidentity}s %{cascade}s", 5,
|
|
"objtype", ObjTypeString, objecttype,
|
|
"concurrently", ObjTypeString, concurrent ? "CONCURRENTLY" : "",
|
|
"if_exists", ObjTypeString, node->missing_ok ? "IF EXISTS" : "",
|
|
"objidentity", ObjTypeString, identity,
|
|
"cascade", ObjTypeString, node->behavior == DROP_CASCADE ? "CASCADE" : "");
|
|
|
|
jsonb = objtree_to_jsonb(stmt, NULL);
|
|
command = JsonbToCString(&str, VARDATA(jsonb), JSONB_ESTIMATED_LEN);
|
|
|
|
return command;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CreateEnumStmt (CREATE TYPE AS ENUM)
|
|
*
|
|
* Given a Enum type OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE TYPE %{identity}D AS ENUM (%{values:, }L)
|
|
*/
|
|
static ObjTree* deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CreateEnumStmt *node = (CreateEnumStmt *) parsetree;
|
|
List *values = NIL;
|
|
ListCell *cell;
|
|
|
|
foreach(cell, node->vals) {
|
|
Value *val = (Value*)lfirst(cell);
|
|
values = lappend(values, new_string_object(strVal(val)));
|
|
}
|
|
|
|
return new_objtree_VA("CREATE TYPE %{identity}D AS ENUM (%{values:, }L)", 2,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname_id(TypeRelationId, objectId),
|
|
"values", ObjTypeArray, values);
|
|
}
|
|
|
|
/*
|
|
* Deparse an AlterObjectSchemaStmt (ALTER ... SET SCHEMA command)
|
|
*
|
|
* Given the object address and the parse tree that created it, return an
|
|
* ObjTree representing the alter command.
|
|
*
|
|
* Verbose syntax
|
|
* ALTER %s %{identity}s SET SCHEMA %{newschema}I
|
|
*/
|
|
static ObjTree* deparse_AlterObjectSchemaStmt(ObjectAddress address, Node *parsetree,
|
|
ObjectAddress old_schema)
|
|
{
|
|
AlterObjectSchemaStmt *node = (AlterObjectSchemaStmt *) parsetree;
|
|
char *identity;
|
|
char *new_schema = node->newschema;
|
|
char *old_schname;
|
|
char *ident;
|
|
|
|
/*
|
|
* Since the command has already taken place from the point of view of
|
|
* catalogs, getObjectIdentity returns the object name with the already
|
|
* changed schema. The output of our deparsing must return the original
|
|
* schema name, however, so we chop the schema name off the identity
|
|
* string and then prepend the quoted schema name.
|
|
*
|
|
* XXX This is pretty clunky. Can we do better?
|
|
*/
|
|
identity = getObjectIdentity(&address);
|
|
old_schname = get_namespace_name(old_schema.objectId);
|
|
if (!old_schname)
|
|
elog(ERROR, "cache lookup failed for schema with OID %u",
|
|
old_schema.objectId);
|
|
|
|
ident = psprintf("%s%s", quote_identifier(old_schname),
|
|
identity + strlen(quote_identifier(new_schema)));
|
|
|
|
return new_objtree_VA("ALTER %{objtype}s %{identity}s SET SCHEMA %{newschema}I", 3,
|
|
"objtype", ObjTypeString,
|
|
string_objtype(node->objectType, false),
|
|
"identity", ObjTypeString, ident,
|
|
"newschema", ObjTypeString, new_schema);
|
|
}
|
|
|
|
|
|
static ObjTree* deparse_CreateEventStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
ObjTree *tmp_obj;
|
|
CreateEventStmt *stmt = (CreateEventStmt *) parsetree;
|
|
StringInfoData string_buf;
|
|
initStringInfo(&string_buf);
|
|
|
|
char *ev_status = NULL;
|
|
if (stmt->def_name) {
|
|
appendStringInfo(&string_buf, "DEFINER = %s", stmt->def_name);
|
|
}
|
|
|
|
if (stmt->event_status == EVENT_DISABLE) {
|
|
ev_status = pstrdup("DISABLE");
|
|
} else if (stmt->event_status == EVENT_DISABLE_ON_SLAVE) {
|
|
ev_status = pstrdup("DISABLE ON SLAVE");
|
|
}
|
|
|
|
char *event_name_str = stmt->event_name->relname;
|
|
char *schema_name_str = stmt->event_name->schemaname;
|
|
|
|
if (stmt->interval_time) {
|
|
ret = new_objtree_VA(
|
|
"CREATE %{definer_opt}s EVENT %{if_not_exits}s %{schema}s.%{eventname}s "
|
|
"ON SCHEDULE EVERY %{every_interval}s STARTS '%{start_expr}s'", 6,
|
|
"definer_opt", ObjTypeString, stmt->def_name ? pstrdup(string_buf.data) : "",
|
|
"if_not_exits", ObjTypeString, stmt->if_not_exists ? "IF NOT EXISTS" : "",
|
|
"schema", ObjTypeString, schema_name_str,
|
|
"eventname", ObjTypeString, event_name_str,
|
|
"every_interval", ObjTypeString, parseIntervalExprString(stmt->interval_time),
|
|
"start_expr", ObjTypeString, parseTimeExprString(stmt->start_time_expr));
|
|
} else {
|
|
ret = new_objtree_VA(
|
|
"CREATE %{definer_opt}s EVENT %{if_not_exits}s %{schema}s.%{eventname}s "
|
|
"ON SCHEDULE STARTS '%{start_expr}s'", 5,
|
|
"definer_opt", ObjTypeString, stmt->def_name ? pstrdup(string_buf.data) : "",
|
|
"if_not_exits", ObjTypeString, stmt->if_not_exists ? "IF NOT EXISTS" : "",
|
|
"schema", ObjTypeString, schema_name_str,
|
|
"eventname", ObjTypeString, event_name_str,
|
|
"start_expr", ObjTypeString, parseTimeExprString(stmt->start_time_expr));
|
|
}
|
|
if (stmt->end_time_expr) {
|
|
append_string_object(ret, "ENDS '%{end_expr}s'",
|
|
"end_expr", parseTimeExprString(stmt->end_time_expr));
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("%{opt_ev_on_completion}s %{opt_ev_status}s COMMENT '%{comment_opt}s' DO %{ev_body}s", 4,
|
|
"opt_ev_on_completion", ObjTypeString,
|
|
stmt->complete_preserve ? "ON COMPLETION NOT PRESERVE" : "ON COMPLETION PRESERVE",
|
|
"opt_ev_status", ObjTypeString, ev_status ? ev_status : "",
|
|
"comment_opt", ObjTypeString, stmt->event_comment_str ? stmt->event_comment_str : "",
|
|
"ev_body", ObjTypeString, stmt->event_query_str);
|
|
|
|
append_object_object(ret, "%{event_body}s", tmp_obj);
|
|
|
|
FreeStringInfo(&string_buf);
|
|
return ret;
|
|
}
|
|
|
|
static ObjTree* deparse_AlterEventStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
AlterEventStmt *stmt = (AlterEventStmt *) parsetree;
|
|
StringInfoData string_buf;
|
|
initStringInfo(&string_buf);
|
|
|
|
char *event_name_str = stmt->event_name->relname;
|
|
char *schema_name_str = stmt->event_name->schemaname;
|
|
|
|
if (stmt->def_name) {
|
|
Value *definerVal = (Value *)stmt->def_name->arg;
|
|
appendStringInfo(&string_buf, "DEFINER = %s", strVal(definerVal));
|
|
}
|
|
|
|
ret = new_objtree_VA("ALTER %{definer_opt}s EVENT %{schema}s.%{eventname}s ", 3,
|
|
"definer_opt", ObjTypeString, stmt->def_name ? pstrdup(string_buf.data) : "",
|
|
"schema", ObjTypeString, schema_name_str,
|
|
"eventname", ObjTypeString, event_name_str);
|
|
|
|
if (stmt->interval_time || stmt->start_time_expr || stmt->end_time_expr) {
|
|
append_format_string(ret, "ON SCHEDULE ");
|
|
if (stmt->interval_time && stmt->interval_time->arg) {
|
|
append_string_object(ret, "EVERY %{every_interval}s ",
|
|
"every_interval", parseIntervalExprString(stmt->interval_time->arg));
|
|
}
|
|
if (stmt->start_time_expr && stmt->start_time_expr->arg) {
|
|
if (stmt->interval_time && stmt->interval_time->arg) {
|
|
append_string_object(ret, "STARTS '%{start_expr}s' ",
|
|
"start_expr", parseTimeExprString(stmt->start_time_expr->arg));
|
|
} else {
|
|
append_string_object(ret, "AT '%{start_expr}s' ",
|
|
"start_expr", parseTimeExprString(stmt->start_time_expr->arg));
|
|
}
|
|
}
|
|
if (stmt->end_time_expr && stmt->end_time_expr->arg) {
|
|
append_string_object(ret, "ENDS '%{end_expr}s' ",
|
|
"end_expr", parseTimeExprString(stmt->end_time_expr->arg));
|
|
}
|
|
}
|
|
|
|
/* preserve_opt rename_opt status_opt comments_opt action_opt */
|
|
if (stmt->complete_preserve && stmt->complete_preserve->arg) {
|
|
Value *arg = (Value *)stmt->complete_preserve->arg;
|
|
if (!intVal(arg)) {
|
|
append_format_string(ret, "ON COMPLETION PRESERVE ");
|
|
} else {
|
|
append_format_string(ret, "ON COMPLETION NOT PRESERVE ");
|
|
}
|
|
}
|
|
|
|
if (stmt->new_name && stmt->new_name->arg) {
|
|
Value *arg = (Value *)stmt->new_name->arg;
|
|
append_string_object(ret, "RENAME TO %{new_name}s ",
|
|
"new_name", strVal(arg));
|
|
}
|
|
|
|
if (stmt->event_status && stmt->event_status->arg) {
|
|
Value *arg = (Value *)stmt->event_status->arg;
|
|
EventStatus ev_status = (EventStatus)intVal(arg);
|
|
if (ev_status == EVENT_ENABLE) {
|
|
append_format_string(ret, "ENABLE ");
|
|
} else if (ev_status == EVENT_DISABLE) {
|
|
append_format_string(ret, "DISABLE ");
|
|
} else if (ev_status == EVENT_DISABLE_ON_SLAVE) {
|
|
append_format_string(ret, "DISABLE ON SLAVE ");
|
|
}
|
|
}
|
|
if (stmt->event_comment_str && stmt->event_comment_str->arg) {
|
|
Value *arg = (Value *)stmt->event_comment_str->arg;
|
|
append_string_object(ret, "COMMENT '%{comment}s' ",
|
|
"comment", strVal(arg));
|
|
}
|
|
if (stmt->event_query_str && stmt->event_query_str) {
|
|
Value *arg = (Value *)stmt->event_query_str->arg;
|
|
append_string_object(ret, "DO %{action}s ",
|
|
"action", strVal(arg));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ObjTree* deparse_DropEventStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
DropEventStmt *stmt = (DropEventStmt *) parsetree;
|
|
char *event_name_str = stmt->event_name->relname;
|
|
char *schema_name_str = stmt->event_name->schemaname;
|
|
|
|
ret = new_objtree_VA("DROP EVENT %{if_exists}s %{schema}s.%{eventname}s", 3,
|
|
"if_exists", ObjTypeString, stmt->missing_ok ? "IF EXISTS" : "",
|
|
"schema", ObjTypeString, schema_name_str,
|
|
"eventname", ObjTypeString, event_name_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Handle deparsing of simple commands.
|
|
*
|
|
* This function should cover all cases handled in ProcessUtilitySlow.
|
|
*/
|
|
static ObjTree* deparse_simple_command(CollectedCommand *cmd, bool *include_owner)
|
|
{
|
|
Oid objectId;
|
|
Node *parsetree;
|
|
|
|
Assert(cmd->type == SCT_Simple);
|
|
|
|
parsetree = cmd->parsetree;
|
|
objectId = cmd->d.simple.address.objectId;
|
|
|
|
if (cmd->in_extension && (nodeTag(parsetree) != T_CreateExtensionStmt))
|
|
return NULL;
|
|
|
|
/* This switch needs to handle everything that ProcessUtilitySlow does */
|
|
switch (nodeTag(parsetree)) {
|
|
case T_AlterFunctionStmt:
|
|
*include_owner = false;
|
|
return deparse_AlterFunction(objectId, parsetree);
|
|
case T_AlterObjectSchemaStmt:
|
|
*include_owner = false;
|
|
return deparse_AlterObjectSchemaStmt(cmd->d.simple.address,
|
|
parsetree,
|
|
cmd->d.simple.secondaryObject);
|
|
case T_AlterSchemaStmt:
|
|
*include_owner = false;
|
|
return deparse_AlterSchemaStmt(objectId, parsetree);
|
|
case T_AlterSeqStmt:
|
|
*include_owner = false;
|
|
return deparse_AlterSeqStmt(objectId, parsetree);
|
|
|
|
case T_CommentStmt:
|
|
*include_owner = false;
|
|
return deparse_CommentStmt(cmd->d.simple.address, parsetree);
|
|
|
|
case T_CompositeTypeStmt:
|
|
return deparse_CompositeTypeStmt(objectId, parsetree);
|
|
|
|
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
|
|
return deparse_CreateEnumStmt(objectId, parsetree);
|
|
|
|
case T_CreateFunctionStmt:
|
|
return deparse_CreateFunction(objectId, parsetree);
|
|
|
|
case T_CreateSchemaStmt:
|
|
return deparse_CreateSchemaStmt(objectId, parsetree);
|
|
|
|
case T_CreateSeqStmt:
|
|
return deparse_CreateSeqStmt(objectId, parsetree);
|
|
case T_CreateStmt:
|
|
return deparse_CreateStmt(objectId, parsetree);
|
|
case T_CreateTrigStmt:
|
|
return deparse_CreateTrigStmt(objectId, parsetree);
|
|
|
|
case T_IndexStmt:
|
|
return deparse_IndexStmt(objectId, parsetree);
|
|
case T_RenameStmt:
|
|
*include_owner = false;
|
|
return deparse_RenameStmt(cmd->d.simple.address, parsetree);
|
|
case T_ViewStmt:
|
|
return deparse_ViewStmt(objectId, parsetree);
|
|
case T_CreateEventStmt:
|
|
return deparse_CreateEventStmt(objectId, parsetree);
|
|
case T_AlterEventStmt:
|
|
return deparse_AlterEventStmt(objectId, parsetree);
|
|
case T_DropEventStmt:
|
|
return deparse_DropEventStmt(objectId, parsetree);
|
|
default:
|
|
if (u_sess->hook_cxt.deparseCollectedCommandHook != NULL) {
|
|
return (ObjTree*)((deparseCollectedCommand)(u_sess->hook_cxt.deparseCollectedCommandHook))
|
|
(DEPARSE_SIMPLE_COMMAND, cmd, NULL, NULL);
|
|
}
|
|
elog(INFO, "unrecognized node type in deparse command: %d",
|
|
(int) nodeTag(parsetree));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* ... ALTER COLUMN ... SET/RESET (...)
|
|
*
|
|
* Verbose syntax
|
|
* ALTER COLUMN %{column}I RESET|SET (%{options:, }s)
|
|
*/
|
|
static ObjTree* deparse_ColumnSetOptions(AlterTableCmd *subcmd)
|
|
{
|
|
List *sets = NIL;
|
|
ListCell *cell;
|
|
ObjTree *ret;
|
|
bool is_reset = subcmd->subtype == AT_ResetOptions;
|
|
|
|
ret = new_objtree_VA("ALTER COLUMN %{column}I %{option}s", 2,
|
|
"column", ObjTypeString, subcmd->name,
|
|
"option", ObjTypeString, is_reset ? "RESET" : "SET");
|
|
|
|
foreach(cell, (List *) subcmd->def) {
|
|
DefElem *elem;
|
|
ObjTree *set;
|
|
|
|
elem = (DefElem *) lfirst(cell);
|
|
set = deparse_DefElem(elem, is_reset);
|
|
sets = lappend(sets, new_object_object(set));
|
|
}
|
|
|
|
Assert(sets);
|
|
append_array_object(ret, "(%{options:, }s)", sets);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* ... ALTER COLUMN ... SET/RESET (...)
|
|
*
|
|
* Verbose syntax
|
|
* RESET|SET (%{options:, }s)
|
|
*/
|
|
static ObjTree* deparse_RelSetOptions(AlterTableCmd *subcmd)
|
|
{
|
|
List *sets = NIL;
|
|
ListCell *cell;
|
|
bool is_reset = subcmd->subtype == AT_ResetRelOptions;
|
|
|
|
foreach(cell, (List *) subcmd->def) {
|
|
DefElem *elem;
|
|
ObjTree *set;
|
|
|
|
elem = (DefElem *) lfirst(cell);
|
|
set = deparse_DefElem(elem, is_reset);
|
|
sets = lappend(sets, new_object_object(set));
|
|
}
|
|
|
|
Assert(sets);
|
|
|
|
return new_objtree_VA("%{set_reset}s (%{options:, }s)", 2,
|
|
"set_reset", ObjTypeString, is_reset ? "RESET" : "SET",
|
|
"options", ObjTypeArray, sets);
|
|
}
|
|
|
|
/*
|
|
* Deparse all the collected subcommands and return an ObjTree representing the
|
|
* alter command.
|
|
*
|
|
* Verbose syntax
|
|
* ALTER reltype %{only}s %{identity}D %{subcmds:, }s
|
|
*/
|
|
static ObjTree* deparse_AlterRelation(CollectedCommand *cmd, ddl_deparse_context *context)
|
|
{
|
|
ObjTree *ret;
|
|
ObjTree *tmp_obj = NULL;
|
|
ObjTree *tmp_obj2;
|
|
List *dpcontext;
|
|
Relation rel;
|
|
List *subcmds = NIL;
|
|
ListCell *cell;
|
|
const char *reltype = NULL;
|
|
bool istype = false;
|
|
bool istable = false;
|
|
bool isrewrite = false;
|
|
List *exprs = NIL;
|
|
Oid relId = cmd->d.alterTable.objectId;
|
|
AlterTableStmt *stmt = NULL;
|
|
bool isonly = false;
|
|
ObjTree *loc_obj;
|
|
|
|
Assert(cmd->type == SCT_AlterTable);
|
|
|
|
stmt = (AlterTableStmt *) cmd->parsetree;
|
|
if (stmt && !IsA(stmt, AlterTableStmt)) {
|
|
return NULL;
|
|
}
|
|
|
|
isrewrite = cmd->d.alterTable.rewrite;
|
|
|
|
/*
|
|
* ALTER TABLE subcommands generated for TableLikeClause is processed in
|
|
* the top level CREATE TABLE command; return empty here.
|
|
*/
|
|
rel = relation_open(relId, AccessShareLock);
|
|
dpcontext = deparse_context_for(RelationGetRelationName(rel),
|
|
relId);
|
|
|
|
switch (rel->rd_rel->relkind) {
|
|
case RELKIND_RELATION:
|
|
reltype = "TABLE";
|
|
istable = true;
|
|
if (stmt->relation->inhOpt == INH_NO) {
|
|
isonly = true;
|
|
}
|
|
break;
|
|
case RELKIND_INDEX:
|
|
case RELKIND_GLOBAL_INDEX:
|
|
reltype = "INDEX";
|
|
break;
|
|
case RELKIND_VIEW:
|
|
reltype = "VIEW";
|
|
break;
|
|
case RELKIND_COMPOSITE_TYPE:
|
|
reltype = "TYPE";
|
|
istype = true;
|
|
break;
|
|
case RELKIND_FOREIGN_TABLE:
|
|
reltype = "FOREIGN TABLE";
|
|
break;
|
|
case RELKIND_MATVIEW:
|
|
reltype = "MATERIALIZED VIEW";
|
|
break;
|
|
|
|
case RELKIND_SEQUENCE:
|
|
reltype = "SEQUENCE";
|
|
break;
|
|
case RELKIND_LARGE_SEQUENCE:
|
|
reltype = "LARGE SEQUENCE";
|
|
break;
|
|
default:
|
|
elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
|
|
}
|
|
|
|
ret = new_objtree_VA("ALTER %{objtype}s %{only}s %{identity}D", 3,
|
|
"objtype", ObjTypeString, reltype,
|
|
"only", ObjTypeString, isonly ? "ONLY" : "",
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname(rel->rd_rel->relnamespace,
|
|
RelationGetRelationName(rel)));
|
|
|
|
foreach(cell, cmd->d.alterTable.subcmds) {
|
|
CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell);
|
|
AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree;
|
|
ObjTree *tree;
|
|
|
|
Assert(IsA(subcmd, AlterTableCmd));
|
|
|
|
/*
|
|
* Skip deparse of the subcommand if the objectId doesn't match the
|
|
* target relation ID. It can happen for inherited tables when
|
|
* subcommands for inherited tables and the parent table are both
|
|
* collected in the ALTER TABLE command for the parent table. With the
|
|
* exception of the internally generated AddConstraint (for
|
|
* ALTER TABLE ADD CONSTRAINT FOREIGN KEY REFERENCES) where the
|
|
* objectIds could mismatch (forein table id and the referenced table
|
|
* id).
|
|
*/
|
|
if (subcmd->recursing)
|
|
continue;
|
|
|
|
switch (subcmd->subtype) {
|
|
case AT_AddColumn:
|
|
case AT_AddColumnRecurse:
|
|
/* XXX need to set the "recurse" bit somewhere? */
|
|
Assert(IsA(subcmd->def, ColumnDef));
|
|
|
|
if (istype && subcmd->subtype == AT_AddColumnRecurse) {
|
|
/* maybe is recurse subcmd for alter type command */
|
|
break;
|
|
}
|
|
|
|
tree = deparse_ColumnDef(rel, dpcontext, false,
|
|
(ColumnDef *) subcmd->def, true, &exprs);
|
|
mark_function_volatile(context, (Node*)exprs);
|
|
tmp_obj = new_objtree_VA("ADD %{objtype}s %{if_not_exists}s %{definition}s %{cascade}s", 5,
|
|
"objtype", ObjTypeString,
|
|
istype ? "ATTRIBUTE" : "COLUMN",
|
|
"type", ObjTypeString, "add column",
|
|
"if_not_exists", ObjTypeString,
|
|
subcmd->missing_ok ? "IF NOT EXISTS" : "",
|
|
"definition", ObjTypeObject, tree,
|
|
"cascade", ObjTypeString, subcmd->behavior == DROP_CASCADE ? "CASCADE" : "");
|
|
|
|
if (subcmd->is_first) {
|
|
loc_obj = new_objtree("FIRST");
|
|
append_object_object(tmp_obj, "%{add_first}s", loc_obj);
|
|
} else if (subcmd->after_name) {
|
|
loc_obj = new_objtree_VA("AFTER %{name}I", 1, "name", ObjTypeString, subcmd->after_name);
|
|
append_object_object(tmp_obj, "%{add_after_name}s", loc_obj);
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
|
|
break;
|
|
case AT_AddIndex: {
|
|
Oid idxOid = sub->address.objectId;
|
|
IndexStmt *istmt;
|
|
Relation idx;
|
|
const char *idxname;
|
|
Oid constrOid;
|
|
|
|
Assert(IsA(subcmd->def, IndexStmt));
|
|
istmt = (IndexStmt *) subcmd->def;
|
|
|
|
if (!istmt->isconstraint)
|
|
break;
|
|
|
|
if (isrewrite)
|
|
break;
|
|
|
|
idx = relation_open(idxOid, AccessShareLock);
|
|
idxname = RelationGetRelationName(idx);
|
|
|
|
constrOid = get_relation_constraint_oid(cmd->d.alterTable.objectId, idxname, false);
|
|
|
|
tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
|
|
"type", ObjTypeString, "add constraint",
|
|
"name", ObjTypeString, idxname,
|
|
"definition", ObjTypeString,
|
|
pg_get_constraintdef_part_string(constrOid));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
|
|
relation_close(idx, AccessShareLock);
|
|
}
|
|
break;
|
|
case AT_AddIndexConstraint: {
|
|
IndexStmt *istmt;
|
|
Relation idx;
|
|
Oid constrOid = sub->address.objectId;
|
|
char *indexname = NULL;
|
|
ListCell *lcell;
|
|
Assert(IsA(subcmd->def, IndexStmt));
|
|
istmt = (IndexStmt *) subcmd->def;
|
|
|
|
Assert(istmt->isconstraint && istmt->unique);
|
|
|
|
idx = relation_open(istmt->indexOid, AccessShareLock);
|
|
|
|
foreach (lcell, istmt->options) {
|
|
DefElem* def = (DefElem*)lfirst(lcell);
|
|
if (pg_strcasecmp(def->defname, "origin_indexname") == 0) {
|
|
indexname = defGetString(def);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Verbose syntax
|
|
*
|
|
* ADD CONSTRAINT %{name}I %{constraint_type}s USING INDEX
|
|
* %index_name}I %{deferrable}s %{init_deferred}s
|
|
*/
|
|
tmp_obj = new_objtree_VA(
|
|
"ADD CONSTRAINT %{name}I %{constraint_type}s "
|
|
"USING INDEX %{index_name}I %{deferrable}s %{init_deferred}s", 6,
|
|
"type", ObjTypeString, "add constraint using index",
|
|
"name", ObjTypeString, get_constraint_name(constrOid),
|
|
"constraint_type", ObjTypeString,
|
|
istmt->primary ? "PRIMARY KEY" : "UNIQUE",
|
|
"index_name", ObjTypeString,
|
|
indexname ? indexname : RelationGetRelationName(idx),
|
|
"deferrable", ObjTypeString,
|
|
istmt->deferrable ? "DEFERRABLE" : "NOT DEFERRABLE",
|
|
"init_deferred", ObjTypeString,
|
|
istmt->initdeferred ? "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
|
|
relation_close(idx, AccessShareLock);
|
|
}
|
|
break;
|
|
|
|
case AT_ReAddIndex:
|
|
case AT_ReAddConstraint:
|
|
case AT_ReplaceRelOptions:
|
|
/* Subtypes used for internal operations; nothing to do here */
|
|
break;
|
|
|
|
case AT_AddColumnToView:
|
|
/* CREATE OR REPLACE VIEW -- nothing to do here */
|
|
break;
|
|
|
|
case AT_ColumnDefault:
|
|
if (subcmd->def == NULL) {
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT", 2,
|
|
"type", ObjTypeString, "drop default",
|
|
"column", ObjTypeString, subcmd->name);
|
|
} else {
|
|
List *dpcontext_rel;
|
|
HeapTuple attrtup;
|
|
AttrNumber attno;
|
|
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT", 2,
|
|
"type", ObjTypeString, "set default",
|
|
"column", ObjTypeString, subcmd->name);
|
|
|
|
dpcontext_rel = deparse_context_for(RelationGetRelationName(rel),
|
|
RelationGetRelid(rel));
|
|
attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
|
|
attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
|
|
append_string_object(tmp_obj, "%{definition}s", "definition",
|
|
RelationGetColumnDefault(rel, attno,
|
|
dpcontext_rel,
|
|
NULL));
|
|
ReleaseSysCache(attrtup);
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropNotNull:
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL", 2,
|
|
"type", ObjTypeString, "drop not null",
|
|
"column", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_SetNotNull:
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL", 2,
|
|
"type", ObjTypeString, "set not null",
|
|
"column", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_SetStatistics: {
|
|
Assert(IsA(subcmd->def, Integer));
|
|
if (subcmd->additional_property) {
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n", 3,
|
|
"type", ObjTypeString, "set statistics",
|
|
"column", ObjTypeString, subcmd->name,
|
|
"statistics", ObjTypeInteger,
|
|
intVal((Value *)subcmd->def));
|
|
} else {
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}n SET STATISTICS PERCENT %{statistics}n", 4,
|
|
"type", ObjTypeString, "set statistics",
|
|
"column", ObjTypeString, subcmd->name,
|
|
"statistics", ObjTypeInteger,
|
|
intVal((Value *)subcmd->def));
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_SetOptions:
|
|
case AT_ResetOptions:
|
|
subcmds = lappend(subcmds, new_object_object(deparse_ColumnSetOptions(subcmd)));
|
|
break;
|
|
|
|
case AT_SetStorage:
|
|
Assert(IsA(subcmd->def, String));
|
|
tmp_obj = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s", 3,
|
|
"type", ObjTypeString, "set storage",
|
|
"column", ObjTypeString, subcmd->name,
|
|
"storage", ObjTypeString,
|
|
strVal((Value *)subcmd->def));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_SET_COMPRESS:
|
|
tmp_obj = new_objtree_VA("SET %{compression}s", 2,
|
|
"type", ObjTypeString, "set compression",
|
|
"compression", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_EnableRowMoveMent:
|
|
tmp_obj = new_objtree_VA("ENABLE ROW MOVEMENT", 1,
|
|
"type", ObjTypeString, "enable row movement");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_DisableRowMoveMent:
|
|
tmp_obj = new_objtree_VA("DISABLE ROW MOVEMENT", 1,
|
|
"type", ObjTypeString, "disable row movement");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_SetAutoIncrement:
|
|
tmp_obj = new_objtree_VA("AUTO_INCREMENT", 1,
|
|
"type", ObjTypeString, "auto_increment");
|
|
if (subcmd->def && IsA(subcmd->def, Integer)) {
|
|
append_int_object(tmp_obj, "%{autoincstart}n", (int32)intVal(subcmd->def));
|
|
} else {
|
|
append_string_object(tmp_obj, "%{autoincstart}s", "autoincstart", strVal(subcmd->def));
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_COMMENTS:
|
|
tmp_obj = new_objtree_VA("COMMENT=%{comment}L", 2,
|
|
"type", ObjTypeString, "comment",
|
|
"comment", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_DropColumn:
|
|
case AT_DropColumnRecurse:
|
|
if (istype && subcmd->subtype == AT_DropColumnRecurse) {
|
|
/* maybe is recurse subcmd for alter type command */
|
|
break;
|
|
}
|
|
tmp_obj = new_objtree_VA("DROP %{objtype}s %{if_exists}s %{column}I", 4,
|
|
"objtype", ObjTypeString,
|
|
istype ? "ATTRIBUTE" : "COLUMN",
|
|
"type", ObjTypeString, "drop column",
|
|
"if_exists", ObjTypeString,
|
|
subcmd->missing_ok ? "IF EXISTS" : "",
|
|
"column", ObjTypeString, subcmd->name);
|
|
tmp_obj2 = new_objtree_VA("CASCADE", 1,
|
|
"present", ObjTypeBool, subcmd->behavior);
|
|
append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_AddConstraint:
|
|
case AT_AddConstraintRecurse: {
|
|
/* XXX need to set the "recurse" bit somewhere? */
|
|
Oid constrOid = sub->address.objectId;
|
|
bool isnull;
|
|
HeapTuple tup;
|
|
Datum val;
|
|
Constraint *constr;
|
|
|
|
/* Skip adding constraint for inherits table sub command */
|
|
if (!constrOid)
|
|
continue;
|
|
|
|
Assert(IsA(subcmd->def, Constraint));
|
|
constr = castNode(Constraint, subcmd->def);
|
|
if (!constr->skip_validation) {
|
|
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
|
|
if (HeapTupleIsValid(tup)) {
|
|
char *conbin;
|
|
|
|
/* Fetch constraint expression in parsetree form */
|
|
val = SysCacheGetAttr(CONSTROID, tup,
|
|
Anum_pg_constraint_conbin, &isnull);
|
|
|
|
if (!isnull) {
|
|
conbin = TextDatumGetCString(val);
|
|
exprs = lappend(exprs, stringToNode(conbin));
|
|
mark_function_volatile(context, (Node *)exprs);
|
|
}
|
|
|
|
ReleaseSysCache(tup);
|
|
}
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s", 3,
|
|
"type", ObjTypeString, "add constraint",
|
|
"name", ObjTypeString, get_constraint_name(constrOid),
|
|
"definition", ObjTypeString,
|
|
pg_get_constraintdef_part_string(constrOid));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
|
|
case AT_ValidateConstraint:
|
|
case AT_ValidateConstraintRecurse:
|
|
tmp_obj = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
|
|
"type", ObjTypeString, "validate constraint",
|
|
"constraint", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropConstraint:
|
|
case AT_DropConstraintRecurse:
|
|
if (subcmd->name == NULL && u_sess->attr.attr_sql.dolphin) {
|
|
tmp_obj = new_objtree_VA("DROP PRIMARY KEY", 1,
|
|
"type", ObjTypeString, "drop primary key");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
} else {
|
|
tmp_obj = new_objtree_VA("DROP CONSTRAINT %{if_exists}s %{constraint}I %{cascade}s", 4,
|
|
"type", ObjTypeString, "drop constraint",
|
|
"if_exists", ObjTypeString,
|
|
subcmd->missing_ok ? "IF EXISTS" : "",
|
|
"constraint", ObjTypeString, subcmd->name,
|
|
"cascade", ObjTypeString,
|
|
subcmd->behavior == DROP_CASCADE ? "CASCADE" : "");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
|
|
case AT_AlterColumnType: {
|
|
Form_pg_attribute att;
|
|
ColumnDef *def;
|
|
HeapTuple heapTup;
|
|
int attnum = 0;
|
|
if (istype && (relId != sub->address.objectId)) {
|
|
/* recurse to cascade table for alter type */
|
|
break;
|
|
}
|
|
/* attrnum may be change by modify */
|
|
heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), subcmd->name);
|
|
if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
|
|
ereport(ERROR,
|
|
(errmsg("column \"%s\" of relation \"%s\" does not exist",
|
|
subcmd->name, RelationGetRelationName(rel))));
|
|
att = (Form_pg_attribute) GETSTRUCT(heapTup);
|
|
attnum = att->attnum;
|
|
|
|
def = (ColumnDef *) subcmd->def;
|
|
Assert(IsA(def, ColumnDef));
|
|
|
|
/*
|
|
* Verbose syntax
|
|
*
|
|
* Composite types: ALTER reltype %{column}I SET DATA TYPE
|
|
* %{datatype}T %{collation}s ATTRIBUTE %{cascade}s
|
|
*
|
|
* Normal types: ALTER reltype %{column}I SET DATA TYPE
|
|
* %{datatype}T %{collation}s COLUMN %{using}s
|
|
*/
|
|
tmp_obj = new_objtree_VA("ALTER %{objtype}s %{column}I SET DATA TYPE %{datatype}T", 4,
|
|
"objtype", ObjTypeString,
|
|
istype ? "ATTRIBUTE" : "COLUMN",
|
|
"type", ObjTypeString, "alter column type",
|
|
"column", ObjTypeString, subcmd->name,
|
|
"datatype", ObjTypeObject,
|
|
new_objtree_for_type(att->atttypid,
|
|
att->atttypmod));
|
|
|
|
/* Add a COLLATE clause, if needed */
|
|
tmp_obj2 = new_objtree("COLLATE");
|
|
if (OidIsValid(att->attcollation)) {
|
|
ObjTree *collname;
|
|
|
|
collname = new_objtree_for_qualname_id(CollationRelationId,
|
|
att->attcollation);
|
|
append_object_object(tmp_obj2, "%{name}D", collname);
|
|
} else {
|
|
append_not_present(tmp_obj2, "%{name}D");
|
|
}
|
|
append_object_object(tmp_obj, "%{collation}s", tmp_obj2);
|
|
|
|
/* If not a composite type, add the USING clause */
|
|
if (!istype) {
|
|
/*
|
|
* If there's a USING clause, transformAlterTableStmt
|
|
* ran it through transformExpr and stored the
|
|
* resulting node in cooked_default, which we can use
|
|
* here.
|
|
*/
|
|
tmp_obj2 = new_objtree("USING");
|
|
if (def->raw_default && sub->usingexpr) {
|
|
mark_function_volatile(context, def->cooked_default);
|
|
|
|
if (contain_mutable_functions(def->cooked_default)) {
|
|
/*
|
|
* allow using modify the idntity value only if
|
|
* the value is stable, or need to use replident identity
|
|
* attr dml change to output change.
|
|
*/
|
|
check_alter_table_rewrite_replident_change(rel, attnum, "ALTER TYPE USING");
|
|
}
|
|
append_string_object(tmp_obj2, "%{expression}s",
|
|
"expression",
|
|
sub->usingexpr);
|
|
} else {
|
|
append_not_present(tmp_obj2, "%{expression}s");
|
|
}
|
|
|
|
append_object_object(tmp_obj, "%{using}s", tmp_obj2);
|
|
}
|
|
|
|
/* If it's a composite type, add the CASCADE clause */
|
|
if (istype) {
|
|
tmp_obj2 = new_objtree("CASCADE");
|
|
if (subcmd->behavior != DROP_CASCADE)
|
|
append_not_present(tmp_obj2, NULL);
|
|
append_object_object(tmp_obj, "%{cascade}s", tmp_obj2);
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
|
|
#ifdef TODOLIST
|
|
case AT_AlterColumnGenericOptions:
|
|
tmp_obj = deparse_FdwOptions((List *) subcmd->def,
|
|
subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
#endif
|
|
case AT_ChangeOwner:
|
|
tmp_obj = new_objtree_VA("OWNER TO %{owner}I", 2,
|
|
"type", ObjTypeString, "change owner",
|
|
"owner", ObjTypeString,
|
|
"subcmd->newowner");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_ClusterOn:
|
|
tmp_obj = new_objtree_VA("CLUSTER ON %{index}I", 2,
|
|
"type", ObjTypeString, "cluster on",
|
|
"index", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropCluster:
|
|
tmp_obj = new_objtree_VA("SET WITHOUT CLUSTER", 1,
|
|
"type", ObjTypeString, "set without cluster");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropOids:
|
|
tmp_obj = new_objtree_VA("SET WITHOUT OIDS", 1,
|
|
"type", ObjTypeString, "set without oids");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_SetTableSpace:
|
|
tmp_obj = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
|
|
"type", ObjTypeString, "set tablespace",
|
|
"tablespace", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_SetRelOptions:
|
|
case AT_ResetRelOptions:
|
|
subcmds = lappend(subcmds, new_object_object(deparse_RelSetOptions(subcmd)));
|
|
break;
|
|
|
|
case AT_EnableTrig:
|
|
tmp_obj = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
|
|
"type", ObjTypeString, "enable trigger",
|
|
"trigger", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableAlwaysTrig:
|
|
tmp_obj = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
|
|
"type", ObjTypeString, "enable always trigger",
|
|
"trigger", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableReplicaTrig:
|
|
tmp_obj = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
|
|
"type", ObjTypeString, "enable replica trigger",
|
|
"trigger", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DisableTrig:
|
|
tmp_obj = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
|
|
"type", ObjTypeString, "disable trigger",
|
|
"trigger", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableTrigAll:
|
|
tmp_obj = new_objtree_VA("ENABLE TRIGGER ALL", 1,
|
|
"type", ObjTypeString, "enable trigger all");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DisableTrigAll:
|
|
tmp_obj = new_objtree_VA("DISABLE TRIGGER ALL", 1,
|
|
"type", ObjTypeString, "disable trigger all");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableTrigUser:
|
|
tmp_obj = new_objtree_VA("ENABLE TRIGGER USER", 1,
|
|
"type", ObjTypeString, "enable trigger user");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DisableTrigUser:
|
|
tmp_obj = new_objtree_VA("DISABLE TRIGGER USER", 1,
|
|
"type", ObjTypeString, "disable trigger user");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableRule:
|
|
tmp_obj = new_objtree_VA("ENABLE RULE %{rule}I", 2,
|
|
"type", ObjTypeString, "enable rule",
|
|
"rule", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableAlwaysRule:
|
|
tmp_obj = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
|
|
"type", ObjTypeString, "enable always rule",
|
|
"rule", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_EnableReplicaRule:
|
|
tmp_obj = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
|
|
"type", ObjTypeString, "enable replica rule",
|
|
"rule", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DisableRule:
|
|
tmp_obj = new_objtree_VA("DISABLE RULE %{rule}I", 2,
|
|
"type", ObjTypeString, "disable rule",
|
|
"rule", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_AddInherit:
|
|
tmp_obj = new_objtree_VA("INHERIT %{parent}D", 2,
|
|
"type", ObjTypeString, "inherit",
|
|
"parent", ObjTypeObject,
|
|
new_objtree_for_qualname_id(RelationRelationId,
|
|
sub->address.objectId));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropInherit:
|
|
tmp_obj = new_objtree_VA("NO INHERIT %{parent}D", 2,
|
|
"type", ObjTypeString, "drop inherit",
|
|
"parent", ObjTypeObject,
|
|
new_objtree_for_qualname_id(RelationRelationId,
|
|
sub->address.objectId));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_AddOf:
|
|
tmp_obj = new_objtree_VA("OF %{type_of}T", 2,
|
|
"type", ObjTypeString, "add of",
|
|
"type_of", ObjTypeObject,
|
|
new_objtree_for_type(sub->address.objectId, -1));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_DropOf:
|
|
tmp_obj = new_objtree_VA("NOT OF", 1,
|
|
"type", ObjTypeString, "not of");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_ReplicaIdentity:
|
|
tmp_obj = new_objtree_VA("REPLICA IDENTITY", 1, "type", ObjTypeString, "replica identity");
|
|
switch (((ReplicaIdentityStmt *)subcmd->def)->identity_type) {
|
|
case REPLICA_IDENTITY_DEFAULT:
|
|
append_string_object(tmp_obj, "%{ident}s", "ident", "DEFAULT");
|
|
break;
|
|
case REPLICA_IDENTITY_FULL:
|
|
append_string_object(tmp_obj, "%{ident}s", "ident", "FULL");
|
|
break;
|
|
case REPLICA_IDENTITY_NOTHING:
|
|
append_string_object(tmp_obj, "%{ident}s", "ident", "NOTHING");
|
|
break;
|
|
case REPLICA_IDENTITY_INDEX:
|
|
tmp_obj2 = new_objtree_VA("USING INDEX %{index}I", 1, "index", ObjTypeString,
|
|
((ReplicaIdentityStmt *)subcmd->def)->name);
|
|
append_object_object(tmp_obj, "%{ident}s", tmp_obj2);
|
|
break;
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_EnableRls:
|
|
tmp_obj = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
|
|
"type", ObjTypeString, "enable row security");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_DisableRls:
|
|
tmp_obj = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
|
|
"type", ObjTypeString, "disable row security");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_ForceRls:
|
|
tmp_obj = new_objtree_VA("FORCE ROW LEVEL SECURITY", 1,
|
|
"type", ObjTypeString, "disable row security");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
|
|
case AT_NoForceRls:
|
|
tmp_obj = new_objtree_VA("NO FORCE ROW LEVEL SECURITY", 1,
|
|
"type", ObjTypeString, "disable row security");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_InvisibleIndex:
|
|
tmp_obj = new_objtree_VA("ALTER INDEX %{name}I INVISIBLE", 2,
|
|
"type", ObjTypeString, "alter index invisible",
|
|
"name", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_VisibleIndex:
|
|
tmp_obj = new_objtree_VA("ALTER INDEX %{name}I VISIBLE", 2,
|
|
"type", ObjTypeString, "alter index visible",
|
|
"name", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_SetCharsetCollate: {
|
|
CharsetCollateOptions *n = (CharsetCollateOptions *) subcmd->def;
|
|
if (n->charset != PG_INVALID_ENCODING) {
|
|
tmp_obj = new_objtree_VA("DEFAULT CHARACTER SET = %{charset}s", 2, "type", ObjTypeString,
|
|
"default character set", "charset", ObjTypeString,
|
|
pg_encoding_to_char(n->charset));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
if (n->collate) {
|
|
tmp_obj = new_objtree_VA("COLLATE = %{collate}s", 2, "type", ObjTypeString, "collate",
|
|
"collate", ObjTypeString, n->collate);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
}
|
|
break;
|
|
case AT_ConvertCharset: {
|
|
CharsetCollateOptions *cc = (CharsetCollateOptions *)subcmd->def;
|
|
if (cc->charset != PG_INVALID_ENCODING) {
|
|
tmp_obj =
|
|
new_objtree_VA("CONVERT TO CHARACTER SET %{charset}s", 2, "type", ObjTypeString,
|
|
"convert charset", "charset", ObjTypeString, pg_encoding_to_char(cc->charset));
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
if (cc->collate) {
|
|
tmp_obj = new_objtree_VA("COLLATE = %{collate}s", 2,
|
|
"type", ObjTypeString, "collate",
|
|
"collate", ObjTypeString, cc->collate);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
}
|
|
break;
|
|
case AT_ModifyColumn: {
|
|
AttrNumber attnum;
|
|
Oid typid;
|
|
int32 typmod;
|
|
Oid typcollation;
|
|
ColumnDef *def = (ColumnDef *) subcmd->def;
|
|
|
|
attnum = get_attnum(RelationGetRelid(rel), def->colname);
|
|
if (attnum == InvalidAttrNumber) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
|
def->colname, RelationGetRelationName(rel))));
|
|
}
|
|
|
|
get_atttypetypmodcoll(RelationGetRelid(rel), attnum, &typid, &typmod, &typcollation);
|
|
|
|
/* MODIFY_P COLUMN ColId Typename opt_charset ColQualList opt_column_options add_column_first_after
|
|
*/
|
|
if (pg_strcasecmp(subcmd->name, def->colname) == 0) {
|
|
tmp_obj = new_objtree_VA("MODIFY COLUMN %{column}I", 2,
|
|
"type", ObjTypeString, "modify column",
|
|
"column", ObjTypeString, def->colname);
|
|
} else {
|
|
tmp_obj = new_objtree_VA("CHANGE %{ori_column}I %{column}I", 3,
|
|
"type", ObjTypeString, "change coulumn",
|
|
"ori_column", ObjTypeString, subcmd->name,
|
|
"column", ObjTypeString, def->colname);
|
|
}
|
|
append_object_object(tmp_obj, "%{datatype}T",
|
|
new_objtree_for_type(typid, typmod));
|
|
|
|
if (def->typname->charset != PG_INVALID_ENCODING)
|
|
append_string_object(tmp_obj, "CHARACTER SET %{charset}s", "charset",
|
|
pg_encoding_to_char(def->typname->charset));
|
|
|
|
deparse_ColumnDef_constraints(tmp_obj, rel, def, dpcontext, &exprs);
|
|
|
|
if (def->collClause) {
|
|
append_object_object(tmp_obj, "COLLATE %{name}D",
|
|
new_objtree_for_qualname_id(CollationRelationId, typcollation));
|
|
}
|
|
|
|
/* column_options comment will be set by commentStmt in alist */
|
|
|
|
if (subcmd->is_first) {
|
|
append_format_string(tmp_obj, "FIRST");
|
|
} else if (subcmd->after_name) {
|
|
append_string_object(tmp_obj, "AFTER %{col}I", "col", subcmd->after_name);
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_AddPartition: {
|
|
/* ADD PARTITION name */
|
|
AddPartitionState *s = (AddPartitionState*)subcmd->def;
|
|
ListCell *lc;
|
|
|
|
Oid *partkey_types = NULL;
|
|
Oid *subpartkey_types = NULL;
|
|
int partkey_num, subpartkey_num = 0;
|
|
partkey_num = get_partition_key_types(relId, RELKIND_RELATION, &partkey_types);
|
|
|
|
foreach (lc, s->partitionList) {
|
|
Node* partition = (Node*)lfirst(lc);
|
|
|
|
if (IsA(partition, RangePartitionDefState)) {
|
|
/* RangePartitionStartEndDefState will transform the RangePartitionDefState list */
|
|
RangePartitionDefState *p = (RangePartitionDefState *)partition;
|
|
|
|
tmp_obj = new_objtree_VA("ADD PARTITION %{name}I VALUES LESS THAN", 2,
|
|
"type", ObjTypeString, "add partition",
|
|
"name", ObjTypeString, p->partitionName);
|
|
|
|
//----------- parse for maxValueList expr
|
|
Oid partoid = InvalidOid;
|
|
List *boundlist = deparse_partition_boudaries(
|
|
relId, PARTTYPE_PARTITIONED_RELATION, PART_STRATEGY_RANGE,
|
|
p->partitionName, &partoid, partkey_num, partkey_types);
|
|
|
|
append_array_object(tmp_obj, "(%{maxvalues:, }s)", boundlist);
|
|
|
|
if (p->tablespacename) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
|
|
if (p->subPartitionDefState) {
|
|
int subpartkey_num = get_partition_key_types(relId,
|
|
PARTTYPE_PARTITIONED_RELATION, &subpartkey_types);
|
|
|
|
deparse_add_subpartition(tmp_obj, partoid, p->subPartitionDefState,
|
|
subpartkey_num, subpartkey_types);
|
|
}
|
|
} else if (IsA(partition, ListPartitionDefState)) {
|
|
ListPartitionDefState *p = (ListPartitionDefState*)partition;
|
|
tmp_obj = new_objtree_VA("ADD PARTITION %{name}I VALUES", 2,
|
|
"type", ObjTypeString, "add partition",
|
|
"name", ObjTypeString, p->partitionName);
|
|
Oid partoid = InvalidOid;
|
|
|
|
List *boundlist =
|
|
deparse_partition_boudaries(relId, PARTTYPE_PARTITIONED_RELATION, PART_STRATEGY_LIST,
|
|
p->partitionName, &partoid, partkey_num, partkey_types);
|
|
append_array_object(tmp_obj, "(%{maxvalues:, }s)", boundlist);
|
|
|
|
if (p->tablespacename) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
if (p->subPartitionDefState) {
|
|
subpartkey_num = get_partition_key_types(relId, PARTTYPE_PARTITIONED_RELATION,
|
|
&subpartkey_types);
|
|
|
|
deparse_add_subpartition(tmp_obj, partoid, p->subPartitionDefState,
|
|
subpartkey_num, subpartkey_types);
|
|
}
|
|
} else if (IsA(partition, HashPartitionDefState)) {
|
|
HashPartitionDefState* p = (HashPartitionDefState*)partition;
|
|
tmp_obj = new_objtree_VA("ADD PARTITION %{name}I", 2,
|
|
"type", ObjTypeString, "add partition",
|
|
"name", ObjTypeString, p->partitionName);
|
|
if (p->tablespacename) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
} else {
|
|
elog(WARNING, "unsupported AddPartitionState %d for partition table", nodeTag(partition));
|
|
break;
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
}
|
|
break;
|
|
case AT_AddSubPartition: {
|
|
AddSubPartitionState *s = (AddSubPartitionState*)subcmd->def;
|
|
ListCell* lc;
|
|
|
|
tmp_obj = new_objtree_VA("MODIFY PARTITION %{name}I", 2,
|
|
"type", ObjTypeString, "modify partition add subpartition",
|
|
"name", ObjTypeString, s->partitionName);
|
|
foreach(lc, s->subPartitionList) {
|
|
if (IsA(lfirst(lc), RangePartitionDefState)) {
|
|
RangePartitionDefState *p = (RangePartitionDefState*)lfirst(lc);
|
|
append_string_object(tmp_obj, "ADD SUBPARTITION %{subpart}I", "subpart", p->partitionName);
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
append_array_object(tmp_obj, "VALUES LESS THAN (%{maxvalues:, }s)", maxvalues);
|
|
if (p->tablespacename) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
} else {
|
|
ListPartitionDefState *p = (ListPartitionDefState*)lfirst(lc);
|
|
append_string_object(tmp_obj, "ADD SUBPARTITION %{subpart}I", "subpart", p->partitionName);
|
|
List *maxvalues = get_list_partition_maxvalues(p->boundary);
|
|
append_array_object(tmp_obj, "VALUES (%{maxvalues:, }s)", maxvalues);
|
|
if (p->tablespacename) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AT_DropPartition:
|
|
case AT_DropSubPartition: {
|
|
tmp_obj = new_objtree_VA(subcmd->subtype == AT_DropPartition ?
|
|
"DROP PARTITION" : "DROP SUBPARTITION", 1,
|
|
"type", ObjTypeString, "drop partition");
|
|
if (subcmd->name) {
|
|
append_string_object(tmp_obj, "%{name}I", "name", subcmd->name);
|
|
} else {
|
|
RangePartitionDefState* state = (RangePartitionDefState*)subcmd->def;
|
|
/* transformPartitionValue for maxvalues const */
|
|
List *maxvalues = get_range_partition_maxvalues(state->boundary);
|
|
|
|
append_array_object(tmp_obj, "FOR (%{maxvalues:, }s)", maxvalues);
|
|
}
|
|
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_SetPartitionTableSpace: {
|
|
if (!subcmd->def) {
|
|
break;
|
|
}
|
|
if (IsA(subcmd->def, RangePartitionDefState)) {
|
|
RangePartitionDefState *p = (RangePartitionDefState*)subcmd->def;
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
tmp_obj = new_objtree_VA("MOVE PARTITION FOR (%{maxvalues:, }s) TABLESPACE %{tblspc}s", 3,
|
|
"type", ObjTypeString, "move partition",
|
|
"maxvalues", ObjTypeArray, maxvalues,
|
|
"tblspc", ObjTypeString, subcmd->name);
|
|
} else if (IsA(subcmd->def, RangeVar)) {
|
|
tmp_obj = new_objtree_VA("MOVE PARTITION %{partition}I TABLESPACE %{tblspc}s", 3,
|
|
"type", ObjTypeString, "move partition",
|
|
"partition", ObjTypeString, ((RangeVar*)subcmd->def)->relname,
|
|
"tblspc", ObjTypeString, subcmd->name);
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_TruncatePartition: {
|
|
if (subcmd->def) {
|
|
RangePartitionDefState *p = (RangePartitionDefState*)subcmd->def;
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
tmp_obj = new_objtree_VA("TRUNCATE PARTITION FOR (%{maxvalues:, }s)", 2,
|
|
"type", ObjTypeString, "truncate partition",
|
|
"maxvalues", ObjTypeArray, maxvalues);
|
|
} else {
|
|
tmp_obj = new_objtree_VA("TRUNCATE PARTITION %{partition}s", 2,
|
|
"type", ObjTypeString, "truncate partition",
|
|
"partition", ObjTypeString, subcmd->name);
|
|
}
|
|
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_TruncateSubPartition: {
|
|
if (subcmd->def && IsA(subcmd->def, RangePartitionDefState)) {
|
|
RangePartitionDefState *p = (RangePartitionDefState*)subcmd->def;
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
|
|
tmp_obj = new_objtree_VA("TRUNCATE SUBPARTITION FOR (%{maxvalues:, }s) %{gpi}s", 3,
|
|
"type", ObjTypeString, "truncate subpartition",
|
|
"maxvalues", ObjTypeArray, maxvalues,
|
|
"gpi", ObjTypeString, subcmd->alterGPI ? "UPDATE GLOBAL INDEX" : "");
|
|
} else {
|
|
tmp_obj = new_objtree_VA("TRUNCATE SUBPARTITION %{partition}s %{gpi}s", 3,
|
|
"type", ObjTypeString, "truncate subpartition",
|
|
"partition", ObjTypeString, subcmd->name,
|
|
"gpi", ObjTypeString, subcmd->alterGPI ? "UPDATE GLOBAL INDEX" : "");
|
|
}
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_ExchangePartition: {
|
|
if (subcmd->def) {
|
|
RangePartitionDefState *p = (RangePartitionDefState*)subcmd->def;
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
tmp_obj = new_objtree_VA(
|
|
"EXCHANGE %{issub}s FOR (%{maxvalues:, }s) WITH TABLE %{exchange_tbl}D", 4,
|
|
"type", ObjTypeString, "exchange partition",
|
|
"issub", ObjTypeString,
|
|
"PARTITION",
|
|
"maxvalues", ObjTypeArray, maxvalues,
|
|
"exchange_tbl", ObjTypeObject,
|
|
new_objtree_for_qualname_rangevar(subcmd->exchange_with_rel));
|
|
} else {
|
|
tmp_obj = new_objtree_VA(
|
|
"EXCHANGE %{issub}s (%{partition}I) WITH TABLE %{exchange_tbl}D", 4,
|
|
"type", ObjTypeString, "exchange partition",
|
|
"issub", ObjTypeString,
|
|
"PARTITION",
|
|
"partition", ObjTypeString, subcmd->name,
|
|
"exchange_tbl", ObjTypeObject,
|
|
new_objtree_for_qualname_rangevar(subcmd->exchange_with_rel));
|
|
}
|
|
|
|
if (!subcmd->check_validation) {
|
|
append_format_string(tmp_obj, "WITHOUT VALIDATION");
|
|
}
|
|
|
|
if (subcmd->exchange_verbose) {
|
|
append_format_string(tmp_obj, "VERBOSE");
|
|
}
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_MergePartition: {
|
|
ListCell *lc;
|
|
List *name_list = NIL;
|
|
foreach(lc, (List*)subcmd->def) {
|
|
Value* v = (Value*)lfirst(lc);
|
|
char *name = pstrdup(v->val.str);
|
|
name_list = lappend(name_list, new_string_object(name));
|
|
}
|
|
|
|
tmp_obj = new_objtree_VA("MERGE PARTITIONS %{name_list:, }s INTO PARTITION %{partition}s", 3,
|
|
"type", ObjTypeString, "merge partition",
|
|
"name_list", ObjTypeArray, name_list,
|
|
"partition", ObjTypeString, subcmd->name);
|
|
if (subcmd->target_partition_tablespace) {
|
|
append_string_object(tmp_obj, "TABLESPACE %{tblspc}s",
|
|
"tblspc", subcmd->target_partition_tablespace);
|
|
}
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
|
|
case AT_SplitPartition: {
|
|
SplitPartitionState *s = (SplitPartitionState*)subcmd->def;
|
|
bool two_partiiton = false;
|
|
|
|
tmp_obj = new_objtree_VA("SPLIT PARTITION", 1,
|
|
"type", ObjTypeString, "split partition");
|
|
if (s->src_partition_name) {
|
|
append_string_object(tmp_obj, "%{partition}s", "partition",
|
|
s->src_partition_name);
|
|
} else if (s->partition_for_values) {
|
|
List *maxvalues = get_range_partition_maxvalues(s->partition_for_values);
|
|
append_array_object(tmp_obj, "FOR (%{maxvalues:, }s)", maxvalues);
|
|
}
|
|
|
|
if (s->split_point) {
|
|
List *maxvalues = get_range_partition_maxvalues(s->split_point);
|
|
append_array_object(tmp_obj, "AT (%{maxvalues:, }s)", maxvalues);
|
|
|
|
if (list_length(s->dest_partition_define_list) == 2) {
|
|
RangePartitionDefState *p1 =
|
|
(RangePartitionDefState*)linitial(s->dest_partition_define_list);
|
|
RangePartitionDefState *p2 =
|
|
(RangePartitionDefState*)lsecond(s->dest_partition_define_list);
|
|
|
|
ObjTree *split_obj = new_objtree_VA(
|
|
"PARTITION %{name1}s %{tblspc1}s, PARTITION %{name2}s %{tblspc2}s", 4,
|
|
"name1", ObjTypeString, p1->partitionName,
|
|
"tblspc1", ObjTypeString, p1->tablespacename ? p1->tablespacename : "",
|
|
"name2", ObjTypeString, p2->partitionName,
|
|
"tblspc2", ObjTypeString, p2->tablespacename ? p2->tablespacename : "");
|
|
|
|
append_object_object(tmp_obj, "INTO (%{split_list}s)", split_obj);
|
|
two_partiiton = true;
|
|
}
|
|
}
|
|
|
|
if (!two_partiiton) {
|
|
ListCell *lc;
|
|
List *partlist = NIL;
|
|
Oid *partkey_types = NULL;
|
|
int partkey_num = get_partition_key_types(relId, RELKIND_RELATION, &partkey_types);
|
|
|
|
foreach(lc, s->dest_partition_define_list) {
|
|
ObjTree *part_obj;
|
|
RangePartitionDefState *p = (RangePartitionDefState*)lfirst(lc);
|
|
Oid partoid = InvalidOid;
|
|
part_obj = new_objtree_VA("PARTITION %{partname}s", 1,
|
|
"partname", ObjTypeString, p->partitionName);
|
|
|
|
List *boundlist = deparse_partition_boudaries(relId, PARTTYPE_PARTITIONED_RELATION,
|
|
PART_STRATEGY_RANGE, p->partitionName, &partoid, partkey_num, partkey_types);
|
|
|
|
append_array_object(part_obj, "VALUES LESS THAN (%{maxvalues:, }s)", boundlist);
|
|
if (p->tablespacename) {
|
|
append_string_object(part_obj, "TABLESPACE %{tblspc}s", "tblspc", p->tablespacename);
|
|
}
|
|
|
|
partlist = lappend(partlist, new_object_object(part_obj));
|
|
}
|
|
|
|
append_array_object(tmp_obj, "INTO (%{partlist:, }s)", partlist);
|
|
}
|
|
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_SplitSubPartition: {
|
|
SplitPartitionState *s = (SplitPartitionState*)subcmd->def;
|
|
ObjTree *define_list = NULL;
|
|
tmp_obj = new_objtree_VA("SPLIT SUBPARTITION %{name}s", 2,
|
|
"type", ObjTypeString, "split subpartition",
|
|
"name", ObjTypeString, s->src_partition_name);
|
|
|
|
if (s->splitType == LISTSUBPARTITIION) {
|
|
List *maxvalues = get_list_partition_maxvalues(s->newListSubPartitionBoundry);
|
|
|
|
append_array_object(tmp_obj, "VALUES (%{maxvalues:, }s)", maxvalues);
|
|
|
|
if (list_length(s->dest_partition_define_list) == 2) {
|
|
ListPartitionDefState *p1 = (ListPartitionDefState*)linitial(s->dest_partition_define_list);
|
|
ListPartitionDefState *p2 = (ListPartitionDefState*)lsecond(s->dest_partition_define_list);
|
|
|
|
define_list = new_objtree_VA(
|
|
"SUBPARTITION %{name1}s %{tblspc1}s, SUBPARTITION %{name2}s %{tblspc2}s", 4,
|
|
"name1", ObjTypeString, p1->partitionName,
|
|
"tblspc1", ObjTypeString, p1->tablespacename ? p1->tablespacename : "",
|
|
"name2", ObjTypeString, p2->partitionName,
|
|
"tblspc2", ObjTypeString, p2->tablespacename ? p2->tablespacename : "");
|
|
}
|
|
if (define_list) {
|
|
append_object_object(tmp_obj, "INTO (%{define_list}s)", define_list);
|
|
}
|
|
} else if (s->splitType == RANGESUBPARTITIION) {
|
|
List *maxvalues = get_range_partition_maxvalues(s->split_point);
|
|
append_array_object(tmp_obj, "AT (%{maxvalues:, }s)", maxvalues);
|
|
|
|
if (list_length(s->dest_partition_define_list) == 2) {
|
|
RangePartitionDefState *p1 =
|
|
(RangePartitionDefState*)linitial(s->dest_partition_define_list);
|
|
RangePartitionDefState *p2 =
|
|
(RangePartitionDefState*)lsecond(s->dest_partition_define_list);
|
|
|
|
define_list = new_objtree_VA(
|
|
"SUBPARTITION %{name1}s %{tblspc1}s, SUBPARTITION %{name2}s %{tblspc2}s", 4,
|
|
"name1", ObjTypeString, p1->partitionName,
|
|
"tblspc1", ObjTypeString, p1->tablespacename ? p1->tablespacename : "",
|
|
"name2", ObjTypeString, p2->partitionName,
|
|
"tblspc2", ObjTypeString, p2->tablespacename ? p2->tablespacename : "");
|
|
if (define_list) {
|
|
append_object_object(tmp_obj, "INTO (%{define_list}s)", define_list);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (subcmd->alterGPI) {
|
|
append_format_string(tmp_obj, "UPDATE GLOBAL INDEX");
|
|
}
|
|
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_ResetPartitionno: {
|
|
tmp_obj = new_objtree_VA("RESET PARTITION", 1,
|
|
"type", ObjTypeString, "reset partition");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
}
|
|
break;
|
|
case AT_UnusableIndex:
|
|
tmp_obj = new_objtree_VA("UNUSABLE", 1,
|
|
"type", ObjTypeString, "unusable index");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
|
|
break;
|
|
case AT_UnusableIndexPartition:
|
|
tmp_obj = new_objtree_VA("MODIFY PARTITION %{partition_identity}I UNUSABLE ", 2,
|
|
"type", ObjTypeString, "unusable partition index",
|
|
"partition_identity", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_UnusableAllIndexOnPartition: {
|
|
if (!subcmd->def) {
|
|
tmp_obj = new_objtree_VA("MODIFY %{parttype} %{partition_identity}I UNUSABLE LOCAL INDEXES ", 3,
|
|
"type", ObjTypeString, "unusable all partition index",
|
|
"parttype", ObjTypeString, "PARTITION",
|
|
"partition_identity", ObjTypeString, subcmd->name);
|
|
} else if (IsA(subcmd->def, RangePartitionDefState)) {
|
|
RangePartitionDefState *p = (RangePartitionDefState *)subcmd->def;
|
|
List *maxvalues = get_range_partition_maxvalues(p->boundary);
|
|
|
|
tmp_obj = new_objtree_VA("MODIFY %{parttype} FOR (%{maxvalues:, }s) UNUSABLE LOCAL INDEXES ", 3,
|
|
"type", ObjTypeString, "unusable all partition index",
|
|
"parttype", ObjTypeString, "PARTITION",
|
|
"maxvalues", ObjTypeArray, maxvalues);
|
|
}
|
|
}
|
|
break;
|
|
case AT_RebuildIndex:
|
|
tmp_obj = new_objtree_VA("REBUILD", 1,
|
|
"type", ObjTypeString, "rebuild index");
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_RebuildIndexPartition:
|
|
tmp_obj = new_objtree_VA("REBUILD PARTITION %{partition_identity}I", 2,
|
|
"type", ObjTypeString, "rebuild partition index",
|
|
"partition_identity", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
case AT_RebuildAllIndexOnPartition:
|
|
tmp_obj = new_objtree_VA("MODIFY PARTITION %{partition_identity}I REBUILD ALL INDEX", 2,
|
|
"type", ObjTypeString, "rebuild partition all index",
|
|
"partition_identity", ObjTypeString, subcmd->name);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
#ifdef TODOLIST
|
|
case AT_GenericOptions:
|
|
tmp_obj = deparse_FdwOptions((List *) subcmd->def, NULL);
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
#endif
|
|
default:
|
|
if (u_sess->hook_cxt.deparseCollectedCommandHook != NULL) {
|
|
tmp_obj = (ObjTree*)((deparseCollectedCommand)(u_sess->hook_cxt.deparseCollectedCommandHook))
|
|
(ALTER_RELATION_SUBCMD, cmd, sub, context);
|
|
if (tmp_obj) {
|
|
subcmds = lappend(subcmds, new_object_object(tmp_obj));
|
|
break;
|
|
}
|
|
}
|
|
|
|
elog(WARNING, "unsupported alter table subtype %d for ddl logical replication",
|
|
subcmd->subtype);
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We don't support replicating ALTER TABLE which contains volatile
|
|
* functions because It's possible the functions contain DDL/DML in
|
|
* which case these operations will be executed twice and cause
|
|
* duplicate data. In addition, we don't know whether the tables being
|
|
* accessed by these DDL/DML are published or not. So blindly allowing
|
|
* such functions can allow unintended clauses like the tables
|
|
* accessed in those functions may not even exist on the subscriber.
|
|
*/
|
|
if (contain_volatile_functions((Node *) exprs))
|
|
elog(ERROR, "ALTER TABLE command using volatile function cannot be replicated");
|
|
|
|
/*
|
|
* Clean the list as we already confirmed there is no volatile
|
|
* function.
|
|
*/
|
|
list_free(exprs);
|
|
exprs = NIL;
|
|
}
|
|
|
|
table_close(rel, AccessShareLock);
|
|
|
|
if (list_length(subcmds) == 0)
|
|
return NULL;
|
|
|
|
append_array_object(ret, "%{subcmds:, }s", subcmds);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Workhorse to deparse a CollectedCommand.
|
|
*/
|
|
char* deparse_utility_command(CollectedCommand *cmd, ddl_deparse_context *context)
|
|
{
|
|
OverrideSearchPath *overridePath;
|
|
MemoryContext oldcxt;
|
|
MemoryContext tmpcxt;
|
|
ObjTree *tree;
|
|
char *command = NULL;
|
|
StringInfoData str;
|
|
|
|
/*
|
|
* Allocate everything done by the deparsing routines into a temp context,
|
|
* to avoid having to sprinkle them with memory handling code, but
|
|
* allocate the output StringInfo before switching.
|
|
*/
|
|
initStringInfo(&str);
|
|
tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
|
|
"deparse ctx",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
oldcxt = MemoryContextSwitchTo(tmpcxt);
|
|
|
|
/*
|
|
* Many routines underlying this one will invoke ruleutils.c functionality
|
|
* to obtain deparsed versions of expressions. In such results, we want
|
|
* all object names to be qualified, so that results are "portable" to
|
|
* environments with different search_path settings. Rather than inject
|
|
* what would be repetitive calls to override search path all over the
|
|
* place, we do it centrally here.
|
|
*/
|
|
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
|
overridePath->schemas = NIL;
|
|
overridePath->addCatalog = false;
|
|
overridePath->addTemp = true;
|
|
PushOverrideSearchPath(overridePath);
|
|
|
|
switch (cmd->type) {
|
|
case SCT_Simple:
|
|
tree = deparse_simple_command(cmd, &context->include_owner);
|
|
break;
|
|
case SCT_AlterTable:
|
|
tree = deparse_AlterRelation(cmd, context);
|
|
context->include_owner = false;
|
|
break;
|
|
default:
|
|
elog(ERROR, "unexpected deparse node type %d", cmd->type);
|
|
}
|
|
|
|
PopOverrideSearchPath();
|
|
|
|
if (tree) {
|
|
Jsonb *jsonb;
|
|
|
|
jsonb = objtree_to_jsonb(tree, context->include_owner ? cmd->role : NULL);
|
|
command = JsonbToCString(&str, VARDATA(jsonb), JSONB_ESTIMATED_LEN);
|
|
}
|
|
|
|
/*
|
|
* Clean up. Note that since we created the StringInfo in the caller's
|
|
* context, the output string is not deleted here.
|
|
*/
|
|
MemoryContextSwitchTo(oldcxt);
|
|
MemoryContextDelete(tmpcxt);
|
|
|
|
return command;
|
|
}
|
|
|
|
|
|
static char* get_maxvalue_from_const(Const* maxvalue_item, char* default_str)
|
|
{
|
|
int16 typlen = 0;
|
|
bool typbyval = false;
|
|
char typalign;
|
|
char typdelim;
|
|
Oid typioparam = InvalidOid;
|
|
Oid outfunc = InvalidOid;
|
|
char* maxvalue;
|
|
char* maxvalue_out;
|
|
|
|
if (constIsNull(maxvalue_item)) {
|
|
maxvalue = pstrdup("NULL");
|
|
} else if (constIsMaxValue(maxvalue_item)) {
|
|
maxvalue = pstrdup(default_str);
|
|
} else {
|
|
get_type_io_data(maxvalue_item->consttype,
|
|
IOFunc_output,
|
|
&typlen,
|
|
&typbyval,
|
|
&typalign,
|
|
&typdelim,
|
|
&typioparam,
|
|
&outfunc);
|
|
maxvalue_out =
|
|
DatumGetCString(OidFunctionCall1Coll(outfunc, maxvalue_item->constcollid, maxvalue_item->constvalue));
|
|
|
|
if (istypestring(maxvalue_item->consttype)) {
|
|
int nret = 0;
|
|
size_t len = strlen(maxvalue_out) + 3;
|
|
maxvalue = (char*)palloc0(len * sizeof(char));
|
|
nret = snprintf_s(maxvalue, len, len - 1, "\'%s\'", maxvalue_out);
|
|
securec_check_ss(nret, "\0", "\0");
|
|
} else {
|
|
maxvalue = pstrdup(maxvalue_out);
|
|
}
|
|
}
|
|
|
|
return maxvalue;
|
|
}
|
|
|
|
static List *get_range_partition_maxvalues(List* boundary)
|
|
{
|
|
ListCell *lc;
|
|
List *maxvalues = NIL;
|
|
|
|
foreach (lc, boundary) {
|
|
Const *maxvalue_item = (Const*)lfirst(lc);
|
|
char *maxvalue = get_maxvalue_from_const(maxvalue_item, "MAXVALUE");
|
|
if (maxvalue)
|
|
maxvalues = lappend(maxvalues, new_string_object(maxvalue));
|
|
}
|
|
|
|
return maxvalues;
|
|
}
|
|
|
|
/* see transformListPartitionValue */
|
|
static List *get_list_partition_maxvalues(List *boundary)
|
|
{
|
|
ListCell *lc;
|
|
List *maxvalues = NIL;
|
|
|
|
foreach (lc, boundary) {
|
|
if (IsA(lfirst(lc), RowExpr)) {
|
|
/* for multi-keys list partition boundary, ((xx,xx),(xx,xx))
|
|
* subpartition's partition key's length should be 1
|
|
*/
|
|
RowExpr *r = (RowExpr*)lfirst(lc);
|
|
ListCell *lc2;
|
|
|
|
StringInfoData tmpbuf;
|
|
int i = 0;
|
|
initStringInfo(&tmpbuf);
|
|
|
|
foreach(lc2, r->args) {
|
|
Const *maxvalue_item = (Const*)lfirst(lc);
|
|
|
|
if (i++ > 0) {
|
|
appendStringInfo(&tmpbuf, ",");
|
|
}
|
|
|
|
char *maxvalue = get_maxvalue_from_const(maxvalue_item, "NULL");
|
|
if (maxvalue) {
|
|
appendStringInfo(&tmpbuf, "%s", maxvalue);
|
|
}
|
|
}
|
|
appendStringInfo(&tmpbuf, ")");
|
|
char* rowstr = pstrdup(tmpbuf.data);
|
|
maxvalues = lappend(maxvalues, new_string_object(rowstr));
|
|
} else {
|
|
/* singel key */
|
|
Const *maxvalue_item = (Const*)lfirst(lc);
|
|
char *maxvalue = get_maxvalue_from_const(maxvalue_item, "DEFAULT");
|
|
if (maxvalue)
|
|
maxvalues = lappend(maxvalues, new_string_object(maxvalue));
|
|
}
|
|
}
|
|
|
|
return maxvalues;
|
|
}
|
|
|
|
static int get_partition_key_types(Oid reloid, char parttype, Oid **partkey_types)
|
|
{
|
|
Relation relation = NULL;
|
|
ScanKeyData key[2];
|
|
SysScanDesc scan = NULL;
|
|
HeapTuple tuple = NULL;
|
|
bool isnull = false;
|
|
bool isPartExprKeyNull = false;
|
|
int partkeynum = 0;
|
|
|
|
relation = heap_open(PartitionRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(parttype));
|
|
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(reloid));
|
|
|
|
scan = systable_beginscan(relation, PartitionParentOidIndexId, true, NULL, 2, key);
|
|
if (HeapTupleIsValid(tuple = systable_getnext(scan))) {
|
|
Datum datum = 0;
|
|
datum = SysCacheGetAttr(PARTRELID, tuple, Anum_pg_partition_partkeyexpr, &isPartExprKeyNull);
|
|
|
|
if (!isPartExprKeyNull) {
|
|
Node *partkeyexpr = NULL;
|
|
char *partkeystr = pstrdup(TextDatumGetCString(datum));
|
|
if (partkeystr)
|
|
partkeyexpr = (Node *)stringToNode_skip_extern_fields(partkeystr);
|
|
else
|
|
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("The partkeystr can't be NULL while getting partition key types")));
|
|
if (!partkeyexpr)
|
|
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("The partkeyexpr can't be NULL while getting partition key types")));
|
|
|
|
partkeynum = 1;
|
|
*partkey_types = (Oid*)palloc0(partkeynum * sizeof(Oid));
|
|
if (partkeyexpr->type == T_OpExpr)
|
|
(*partkey_types)[0] = ((OpExpr*)partkeyexpr)->opresulttype;
|
|
else if (partkeyexpr->type == T_FuncExpr)
|
|
(*partkey_types)[0] = ((FuncExpr*)partkeyexpr)->funcresulttype;
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NODE_ID_MISSMATCH),
|
|
errmsg("The node type %d is wrong, it must be T_OpExpr or T_FuncExpr", partkeyexpr->type)));
|
|
} else {
|
|
Oid *iPartboundary = NULL;
|
|
datum = SysCacheGetAttr(PARTRELID, tuple, Anum_pg_partition_partkey, &isnull);
|
|
|
|
if (isnull) {
|
|
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("can not find partkey while getting partition key types")));
|
|
} else {
|
|
int2vector *partVec = (int2vector *)DatumGetPointer(datum);
|
|
partkeynum = partVec->dim1;
|
|
iPartboundary = (Oid *)palloc0(partkeynum * sizeof(Oid));
|
|
for (int i = 0; i < partVec->dim1; i++) {
|
|
iPartboundary[i] = get_atttype(reloid, partVec->values[i]);
|
|
}
|
|
*partkey_types = iPartboundary;
|
|
}
|
|
}
|
|
} else {
|
|
systable_endscan(scan);
|
|
heap_close(relation, AccessShareLock);
|
|
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("could not find tuple for partition relation %u", reloid)));
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
return partkeynum;
|
|
}
|
|
|
|
static ObjTree *deparse_add_subpartition(ObjTree *ret, Oid partoid, List *subPartitionDefState, int partkeynum,
|
|
Oid *partkey_types)
|
|
{
|
|
ListCell *subcell;
|
|
List *sublist = NIL;
|
|
ObjTree *subobj = NULL;
|
|
Oid suboid = InvalidOid;
|
|
|
|
foreach(subcell, subPartitionDefState) {
|
|
if (IsA((Node*)lfirst(subcell), ListPartitionDefState)) {
|
|
ListPartitionDefState *n = (ListPartitionDefState*)lfirst(subcell);
|
|
|
|
List *subboundlist =
|
|
deparse_partition_boudaries(partoid, PARTTYPE_SUBPARTITIONED_RELATION, PART_STRATEGY_LIST,
|
|
n->partitionName, &suboid, partkeynum, partkey_types);
|
|
|
|
subobj = new_objtree_VA("SUBPARTITION %{name}I VALUES (%{maxvalue:, }s)", 2,
|
|
"name", ObjTypeString, n->partitionName,
|
|
"maxvalue", ObjTypeArray, subboundlist);
|
|
if (n->tablespacename) {
|
|
append_string_object(subobj, "TABLESPACE %{tblspc}s", "tblspc", n->tablespacename);
|
|
}
|
|
} else if (IsA((Node*)lfirst(subcell), HashPartitionDefState)) {
|
|
HashPartitionDefState *n = (HashPartitionDefState*)lfirst(subcell);
|
|
|
|
subobj = new_objtree_VA("SUBPARTITION %{name}I", 1,
|
|
"name", ObjTypeString, n->partitionName);
|
|
if (n->tablespacename) {
|
|
append_string_object(subobj, "TABLESPACE %{tblspc}s", "tblspc", n->tablespacename);
|
|
}
|
|
} else if (IsA((Node*)lfirst(subcell), RangePartitionDefState)) {
|
|
RangePartitionDefState *n = (RangePartitionDefState*)lfirst(subcell);
|
|
|
|
List *subboundlist =
|
|
deparse_partition_boudaries(partoid, PARTTYPE_SUBPARTITIONED_RELATION, PART_STRATEGY_RANGE,
|
|
n->partitionName, &suboid, partkeynum, partkey_types);
|
|
|
|
subobj = new_objtree_VA("SUBPARTITION %{name}I VALUES LESS THAN (%{maxvalue:, }s)", 2,
|
|
"name", ObjTypeString, n->partitionName,
|
|
"maxvalue", ObjTypeArray, subboundlist);
|
|
if (n->tablespacename) {
|
|
append_string_object(subobj, "TABLESPACE %{tblspc}s", "tblspc", n->tablespacename);
|
|
}
|
|
} else {
|
|
elog(ERROR, "unrecognize subpartiiton definition type %d", nodeTag((Node*)lfirst(subcell)));
|
|
}
|
|
sublist = lappend(sublist, new_object_object(subobj));
|
|
}
|
|
|
|
append_array_object(ret, "(%{subpartitions:, }s)", sublist);
|
|
return ret;
|
|
}
|
|
|
|
static List *deparse_partition_boudaries(Oid parentoid, char reltype, char strategy, const char *partition_name,
|
|
Oid *partoid, int partkeynum, Oid *partkey_types)
|
|
{
|
|
List *boundlist = NIL;
|
|
Relation relation = NULL;
|
|
ScanKeyData key[2];
|
|
SysScanDesc scan = NULL;
|
|
HeapTuple tuple = NULL;
|
|
Oid thisoid = InvalidOid;
|
|
char *maxvalue_str = NULL;
|
|
|
|
relation = heap_open(PartitionRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(reltype));
|
|
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(parentoid));
|
|
scan = systable_beginscan(relation, PartitionParentOidIndexId, true, NULL, 2, key);
|
|
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
|
|
bool datum_is_null;
|
|
Form_pg_partition foundpart = (Form_pg_partition)GETSTRUCT(tuple);
|
|
if (pg_strcasecmp(foundpart->relname.data, partition_name)) {
|
|
continue;
|
|
}
|
|
|
|
Datum boundary_datum = SysCacheGetAttr(PARTRELID, tuple, Anum_pg_partition_boundaries, &datum_is_null);
|
|
if (datum_is_null) {
|
|
if (strategy == PART_STRATEGY_LIST) {
|
|
maxvalue_str = pstrdup("DEFAULT");
|
|
boundlist = lappend(boundlist, new_string_object(maxvalue_str));
|
|
thisoid = HeapTupleGetOid(tuple);
|
|
break;
|
|
} else {
|
|
systable_endscan(scan);
|
|
heap_close(relation, AccessShareLock);
|
|
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("could not find boundaries for partition %s", partition_name)));
|
|
}
|
|
}
|
|
|
|
if (strategy == PART_STRATEGY_LIST && partkeynum > 1) {
|
|
/* see unnest function */
|
|
ArrayType* arr = DatumGetArrayTypeP(boundary_datum);
|
|
|
|
Datum dt = 0;
|
|
bool isnull = false;
|
|
|
|
ArrayIterator it = array_create_iterator(arr, 0);
|
|
while (array_iterate(it, &dt, &isnull)) {
|
|
if (isnull) {
|
|
// default
|
|
maxvalue_str = pstrdup("DEFAULT");
|
|
boundlist = lappend(boundlist, new_string_object(maxvalue_str));
|
|
continue;
|
|
}
|
|
|
|
StringInfoData tmpbuf;
|
|
initStringInfo(&tmpbuf);
|
|
|
|
char* partvalue_str = TextDatumGetCString(dt);
|
|
Type targetType = typeidType(TEXTARRAYOID);
|
|
Datum partvalue_array_datum = stringTypeDatum(targetType, partvalue_str, TEXTOID, true);
|
|
ReleaseSysCache(targetType);
|
|
|
|
ArrayType* partvalue_array = DatumGetArrayTypeP(partvalue_array_datum);
|
|
|
|
Datum partkey_dt = 0;
|
|
bool partkey_isnull = false;
|
|
int it_index = 0;
|
|
|
|
appendStringInfo(&tmpbuf, "(");
|
|
ArrayIterator partkey_it = array_create_iterator(partvalue_array, 0);
|
|
while (array_iterate(partkey_it, &partkey_dt, &partkey_isnull)) {
|
|
// OidInputFunctionCall
|
|
if (it_index > 0) {
|
|
appendStringInfo(&tmpbuf, ",");
|
|
}
|
|
|
|
if (partkey_isnull) {
|
|
appendStringInfo(&tmpbuf, "NULL");
|
|
continue;
|
|
}
|
|
|
|
char *svalue = TextDatumGetCString(partkey_dt);
|
|
|
|
if (istypestring(partkey_types[it_index])) {
|
|
appendStringInfo(&tmpbuf, "'%s'", svalue);
|
|
} else {
|
|
appendStringInfo(&tmpbuf, "%s", svalue);
|
|
}
|
|
++it_index;
|
|
}
|
|
appendStringInfo(&tmpbuf, ")");
|
|
maxvalue_str = pstrdup(tmpbuf.data);
|
|
if (maxvalue_str) {
|
|
boundlist = lappend(boundlist, new_string_object(maxvalue_str));
|
|
}
|
|
}
|
|
} else {
|
|
List* boundary = untransformPartitionBoundary(boundary_datum);
|
|
ListCell *bcell;
|
|
int i = 0;
|
|
foreach (bcell, boundary) {
|
|
Value* maxvalue = (Value*)lfirst(bcell);
|
|
|
|
if (i >= partkeynum) {
|
|
break;
|
|
}
|
|
|
|
if (!PointerIsValid(maxvalue->val.str)) {
|
|
if (strategy == PART_STRATEGY_RANGE) {
|
|
maxvalue_str = pstrdup("MAXVALUE");
|
|
} else {
|
|
maxvalue_str = pstrdup("DEFAULT");
|
|
}
|
|
} else if (istypestring(partkey_types[i])) {
|
|
int nret = 0;
|
|
size_t len = strlen(maxvalue->val.str) + 3;
|
|
maxvalue_str = (char *)palloc0(len * sizeof(char));
|
|
nret = snprintf_s(maxvalue_str, len, len - 1, "\'%s\'", maxvalue->val.str);
|
|
securec_check_ss(nret, "\0", "\0");
|
|
} else {
|
|
maxvalue_str = pstrdup(maxvalue->val.str);
|
|
}
|
|
if (strategy == PART_STRATEGY_RANGE)
|
|
++i;
|
|
|
|
if (maxvalue_str)
|
|
boundlist = lappend(boundlist, new_string_object(maxvalue_str));
|
|
}
|
|
}
|
|
|
|
thisoid = HeapTupleGetOid(tuple);
|
|
break;
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
heap_close(relation, AccessShareLock);
|
|
|
|
if (!OidIsValid(thisoid)) {
|
|
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
|
|
errmsg("could not find subpartition tuple for partition relation %s", partition_name)));
|
|
}
|
|
|
|
if (partoid)
|
|
*partoid = thisoid;
|
|
|
|
return boundlist;
|
|
}
|
|
|
|
/*
|
|
* Handle deparsing setting of Function
|
|
*
|
|
* Verbose syntax
|
|
* RESET ALL
|
|
* OR
|
|
* SET %{set_name}I TO %{set_value}s
|
|
* OR
|
|
* RESET %{set_name}I
|
|
*/
|
|
static ObjTree* deparse_FunctionSet(VariableSetKind kind, char *name, char *value)
|
|
{
|
|
ObjTree *ret;
|
|
struct config_generic *record;
|
|
|
|
if (kind == VAR_RESET_ALL) {
|
|
ret = new_objtree("RESET ALL");
|
|
} else if (kind == VAR_SET_VALUE) {
|
|
ret = new_objtree_VA("SET %{set_name}I", 1,
|
|
"set_name", ObjTypeString, name);
|
|
|
|
/*
|
|
* Some GUC variable names are 'LIST' type and hence must not be
|
|
* quoted.
|
|
*/
|
|
record = find_option(name, false, ERROR);
|
|
if (record && (record->flags & GUC_LIST_QUOTE))
|
|
append_string_object(ret, "TO %{set_value}s", "set_value", value);
|
|
else
|
|
append_string_object(ret, "TO %{set_value}L", "set_value", value);
|
|
} else {
|
|
ret = new_objtree_VA("RESET %{set_name}I", 1,
|
|
"set_name", ObjTypeString, name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CreateFunctionStmt (CREATE FUNCTION)
|
|
*
|
|
* Given a function OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
*
|
|
* CREATE %{or_replace}s FUNCTION %{signature}s RETURNS %{return_type}s
|
|
* LANGUAGE %{transform_type}s %{language}I %{window}s %{volatility}s
|
|
* %{parallel_safety}s %{leakproof}s %{strict}s %{security_definer}s
|
|
* %{cost}s %{rows}s %{support}s %{set_options: }s AS %{objfile}L,
|
|
* %{symbol}L
|
|
*/
|
|
static ObjTree* deparse_CreateFunction(Oid objectId, Node *parsetree)
|
|
{
|
|
ObjTree *ret;
|
|
char *func_str;
|
|
size_t len;
|
|
|
|
func_str = pg_get_functiondef_string(objectId);
|
|
len = strlen(func_str);
|
|
if (func_str[len - 1] == '\n' && func_str[len - 2] == '/'
|
|
&& func_str[len - 3] == '\n' && func_str[len - 4] == ';') {
|
|
func_str[len - 2] = '\0';
|
|
func_str[len - 1] = '\0';
|
|
}
|
|
|
|
ret = new_objtree_VA("%{function}s", 1,
|
|
"function", ObjTypeString,
|
|
func_str);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse an AlterFunctionStmt (ALTER FUNCTION/ROUTINE/PROCEDURE)
|
|
*
|
|
* Given a function OID and the parse tree that created it, return an ObjTree
|
|
* representing the alter command.
|
|
*
|
|
* Verbose syntax:
|
|
* ALTER FUNCTION/ROUTINE/PROCEDURE %{signature}s %{definition: }s
|
|
*/
|
|
static ObjTree* deparse_AlterFunction(Oid objectId, Node *parsetree)
|
|
{
|
|
AlterFunctionStmt *node = (AlterFunctionStmt *) parsetree;
|
|
ObjTree *ret;
|
|
ObjTree *sign;
|
|
HeapTuple procTup;
|
|
Form_pg_proc procForm;
|
|
List *params = NIL;
|
|
List *elems = NIL;
|
|
ListCell *cell;
|
|
int i;
|
|
|
|
/* Get the pg_proc tuple */
|
|
procTup = SearchSysCache1(PROCOID, objectId);
|
|
if (!HeapTupleIsValid(procTup))
|
|
elog(ERROR, "cache lookup failed for function with OID %u", objectId);
|
|
procForm = (Form_pg_proc) GETSTRUCT(procTup);
|
|
|
|
if (node->isProcedure)
|
|
ret = new_objtree("ALTER PROCEDURE");
|
|
else
|
|
ret = new_objtree("ALTER FUNCTION");
|
|
|
|
/*
|
|
* ALTER FUNCTION does not change signature so we can use catalog to get
|
|
* input type Oids.
|
|
*/
|
|
for (i = 0; i < procForm->pronargs; i++) {
|
|
ObjTree *tmp_obj;
|
|
|
|
tmp_obj = new_objtree_VA("%{type}T", 1,
|
|
"type", ObjTypeObject,
|
|
new_objtree_for_type(procForm->proargtypes.values[i], -1));
|
|
params = lappend(params, new_object_object(tmp_obj));
|
|
}
|
|
|
|
sign = new_objtree_VA("%{identity}D (%{arguments:, }s)", 2,
|
|
"identity", ObjTypeObject,
|
|
new_objtree_for_qualname_id(ProcedureRelationId, objectId),
|
|
"arguments", ObjTypeArray, params);
|
|
|
|
append_object_object(ret, "%{signature}s", sign);
|
|
|
|
foreach(cell, node->actions) {
|
|
DefElem *defel = (DefElem *) lfirst(cell);
|
|
ObjTree *tmp_obj = NULL;
|
|
|
|
if (strcmp(defel->defname, "volatility") == 0) {
|
|
tmp_obj = new_objtree(strVal(defel->arg));
|
|
} else if (strcmp(defel->defname, "strict") == 0) {
|
|
tmp_obj = new_objtree(intVal(defel->arg) ?
|
|
"RETURNS NULL ON NULL INPUT" :
|
|
"CALLED ON NULL INPUT");
|
|
} else if (strcmp(defel->defname, "security") == 0) {
|
|
tmp_obj = PLSQL_SECURITY_DEFINER ? new_objtree(intVal(defel->arg) ?
|
|
"AUTHID DEFINER" : "AUTHID CURRENT_USER") :
|
|
new_objtree(intVal(defel->arg) ?
|
|
"SECURITY DEFINER" : "SECURITY INVOKER");
|
|
} else if (strcmp(defel->defname, "leakproof") == 0) {
|
|
tmp_obj = new_objtree(intVal(defel->arg) ?
|
|
"LEAKPROOF" : "NOT LEAKPROOF");
|
|
} else if (strcmp(defel->defname, "cost") == 0) {
|
|
tmp_obj = new_objtree_VA("COST %{cost}n", 1,
|
|
"cost", ObjTypeFloat,
|
|
defGetNumeric(defel));
|
|
} else if (strcmp(defel->defname, "rows") == 0) {
|
|
tmp_obj = new_objtree("ROWS");
|
|
if (defGetNumeric(defel) == 0)
|
|
append_not_present(tmp_obj, "%{rows}n");
|
|
else
|
|
append_float_object(tmp_obj, "%{rows}n",
|
|
defGetNumeric(defel));
|
|
} else if (strcmp(defel->defname, "set") == 0) {
|
|
VariableSetStmt *sstmt = (VariableSetStmt *) defel->arg;
|
|
char *value = ExtractSetVariableArgs(sstmt);
|
|
|
|
tmp_obj = deparse_FunctionSet(sstmt->kind, sstmt->name, value);
|
|
} else if (strcmp(defel->defname, "parallel") == 0) {
|
|
tmp_obj = new_objtree_VA("PARALLEL %{value}s", 1,
|
|
"value", ObjTypeString, strVal(defel->arg));
|
|
}
|
|
|
|
elems = lappend(elems, new_object_object(tmp_obj));
|
|
}
|
|
|
|
append_array_object(ret, "%{definition: }s", elems);
|
|
|
|
ReleaseSysCache(procTup);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Deparse a CreateTrigStmt (CREATE TRIGGER)
|
|
*
|
|
* Given a trigger OID and the parse tree that created it, return an ObjTree
|
|
* representing the creation command.
|
|
*
|
|
* Verbose syntax
|
|
* CREATE %{constraint}s TRIGGER %{name}I %{time}s %{events: OR }s ON
|
|
* %{relation}D %{from_table}s %{constraint_attrs: }s %{referencing: }s
|
|
* FOR EACH %{for_each}s %{when}s EXECUTE PROCEDURE %{function}s
|
|
*/
|
|
static ObjTree* deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
|
|
{
|
|
CreateTrigStmt *node = (CreateTrigStmt *) parsetree;
|
|
Relation pg_trigger;
|
|
HeapTuple trigTup;
|
|
Form_pg_trigger trigForm;
|
|
ObjTree *ret;
|
|
ObjTree *tmp_obj;
|
|
int tgnargs;
|
|
List *list = NIL;
|
|
List *events;
|
|
char *trigtiming;
|
|
Datum value;
|
|
bool isnull;
|
|
char *bodySrc;
|
|
|
|
pg_trigger = table_open(TriggerRelationId, AccessShareLock);
|
|
|
|
trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
|
|
trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
|
|
|
|
trigtiming = (char*)(node->timing == TRIGGER_TYPE_BEFORE ? "BEFORE" :
|
|
node->timing == TRIGGER_TYPE_AFTER ? "AFTER" :
|
|
node->timing == TRIGGER_TYPE_INSTEAD ? "INSTEAD OF" :
|
|
NULL);
|
|
if (!trigtiming)
|
|
elog(ERROR, "unrecognized trigger timing type %d", node->timing);
|
|
|
|
/* DEFINER clause */
|
|
tmp_obj = new_objtree("DEFINER");
|
|
if (node->definer)
|
|
append_string_object(tmp_obj, "=%{definer}s", "definer",
|
|
node->definer);
|
|
else
|
|
append_not_present(tmp_obj, "=%{definer}s");
|
|
|
|
ret = new_objtree_VA("CREATE %{definer}s %{constraint}s TRIGGER %{if_not_exists}s %{name}I %{time}s", 5,
|
|
"definer", ObjTypeObject, tmp_obj,
|
|
"constraint", ObjTypeString, node->isconstraint ? "CONSTRAINT" : "",
|
|
"if_not_exists", ObjTypeString, node->if_not_exists ? "IF NOT EXISTS" : "",
|
|
"name", ObjTypeString, node->trigname,
|
|
"time", ObjTypeString, trigtiming);
|
|
|
|
/*
|
|
* Decode the events that the trigger fires for. The output is a list; in
|
|
* most cases it will just be a string with the event name, but when
|
|
* there's an UPDATE with a list of columns, we return a JSON object.
|
|
*/
|
|
events = NIL;
|
|
if (node->events & TRIGGER_TYPE_INSERT)
|
|
events = lappend(events, new_string_object("INSERT"));
|
|
if (node->events & TRIGGER_TYPE_DELETE)
|
|
events = lappend(events, new_string_object("DELETE"));
|
|
if (node->events & TRIGGER_TYPE_TRUNCATE)
|
|
events = lappend(events, new_string_object("TRUNCATE"));
|
|
if (node->events & TRIGGER_TYPE_UPDATE) {
|
|
if (node->columns == NIL) {
|
|
events = lappend(events, new_string_object("UPDATE"));
|
|
} else {
|
|
ObjTree *update;
|
|
ListCell *cell;
|
|
List *cols = NIL;
|
|
|
|
/*
|
|
* Currently only UPDATE OF can be objects in the output JSON, but
|
|
* we add a "kind" element so that user code can distinguish
|
|
* possible future new event types.
|
|
*/
|
|
update = new_objtree_VA("UPDATE OF", 1,
|
|
"kind", ObjTypeString, "update_of");
|
|
|
|
foreach(cell, node->columns) {
|
|
char *colname = strVal(lfirst(cell));
|
|
|
|
cols = lappend(cols, new_string_object(colname));
|
|
}
|
|
|
|
append_array_object(update, "%{columns:, }I", cols);
|
|
|
|
events = lappend(events, new_object_object(update));
|
|
}
|
|
}
|
|
append_array_object(ret, "%{events: OR }s", events);
|
|
|
|
tmp_obj = new_objtree_for_qualname_id(RelationRelationId,
|
|
trigForm->tgrelid);
|
|
append_object_object(ret, "ON %{relation}D", tmp_obj);
|
|
|
|
tmp_obj = new_objtree("FROM");
|
|
if (trigForm->tgconstrrelid) {
|
|
ObjTree *rel;
|
|
|
|
rel = new_objtree_for_qualname_id(RelationRelationId,
|
|
trigForm->tgconstrrelid);
|
|
append_object_object(tmp_obj, "%{relation}D", rel);
|
|
} else {
|
|
append_not_present(tmp_obj, "%{relation}D");
|
|
}
|
|
append_object_object(ret, "%{from_table}s", tmp_obj);
|
|
|
|
if (node->isconstraint) {
|
|
if (!node->deferrable)
|
|
list = lappend(list, new_string_object("NOT"));
|
|
list = lappend(list, new_string_object("DEFERRABLE INITIALLY"));
|
|
if (node->initdeferred)
|
|
list = lappend(list, new_string_object("DEFERRED"));
|
|
else
|
|
list = lappend(list, new_string_object("IMMEDIATE"));
|
|
}
|
|
append_array_object(ret, "%{constraint_attrs: }s", list);
|
|
|
|
append_string_object(ret, "FOR EACH %{for_each}s", "for_each",
|
|
node->row ? "ROW" : "STATEMENT");
|
|
|
|
tmp_obj = new_objtree("WHEN");
|
|
if (node->whenClause) {
|
|
Node *whenClause;
|
|
|
|
value = fastgetattr(trigTup, Anum_pg_trigger_tgqual,
|
|
RelationGetDescr(pg_trigger), &isnull);
|
|
if (isnull)
|
|
elog(ERROR, "null tgqual for trigger \"%s\"",
|
|
NameStr(trigForm->tgname));
|
|
|
|
whenClause = (Node*)stringToNode(TextDatumGetCString(value));
|
|
|
|
append_string_object(tmp_obj, "(%{clause}s)", "clause",
|
|
pg_get_trigger_whenclause(trigForm, whenClause, false));
|
|
} else {
|
|
append_not_present(tmp_obj, "%{clause}s");
|
|
}
|
|
append_object_object(ret, "%{when}s", tmp_obj);
|
|
|
|
if (node->funcname && !node->funcSource) {
|
|
tmp_obj = new_objtree_VA("%{funcname}D", 1, "funcname", ObjTypeObject,
|
|
new_objtree_for_qualname_id(ProcedureRelationId, trigForm->tgfoid));
|
|
list = NIL;
|
|
tgnargs = trigForm->tgnargs;
|
|
if (tgnargs > 0) {
|
|
bytea *tgargs;
|
|
char *argstr;
|
|
int findx;
|
|
int lentgargs;
|
|
char *p;
|
|
|
|
tgargs = DatumGetByteaP(fastgetattr(trigTup,
|
|
Anum_pg_trigger_tgargs,
|
|
RelationGetDescr(pg_trigger),
|
|
&isnull));
|
|
if (isnull)
|
|
elog(ERROR, "null tgargs for trigger \"%s\"", NameStr(trigForm->tgname));
|
|
argstr = (char *)VARDATA(tgargs);
|
|
lentgargs = VARSIZE_ANY_EXHDR(tgargs);
|
|
|
|
p = argstr;
|
|
for (findx = 0; findx < tgnargs; findx++) {
|
|
size_t tlen;
|
|
|
|
/* Verify that the argument encoding is correct */
|
|
tlen = strlen(p);
|
|
if (p + tlen >= argstr + lentgargs) {
|
|
elog(ERROR, "invalid argument string (%s) for trigger \"%s\"", argstr, NameStr(trigForm->tgname));
|
|
}
|
|
list = lappend(list, new_string_object(p));
|
|
p += tlen + 1;
|
|
}
|
|
}
|
|
|
|
append_format_string(tmp_obj, "(");
|
|
append_array_object(tmp_obj, "%{args:, }L", list); /* might be NIL */
|
|
append_format_string(tmp_obj, ")");
|
|
|
|
append_object_object(ret, "EXECUTE PROCEDURE %{function}s", tmp_obj);
|
|
}
|
|
|
|
if (node->funcSource && node->funcSource->bodySrc) {
|
|
bodySrc = pstrdup(node->funcSource->bodySrc);
|
|
if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT
|
|
&& strlen(bodySrc) > BEGIN_P_LEN
|
|
&& pg_strncasecmp(bodySrc, BEGIN_P_STR, BEGIN_P_LEN) == 0) {
|
|
errno_t rc = memcpy_s(bodySrc, strlen(bodySrc), BEGIN_N_STR, BEGIN_P_LEN);
|
|
securec_check(rc, "\0", "\0")
|
|
}
|
|
tmp_obj = new_objtree_VA("%{bodysrc}s", 1,
|
|
"bodysrc", ObjTypeString, bodySrc);
|
|
} else {
|
|
tmp_obj = new_objtree_VA("", 1,
|
|
"present", ObjTypeBool, false);
|
|
}
|
|
append_object_object(ret, "%{bodysrc}s", tmp_obj);
|
|
|
|
table_close(pg_trigger, AccessShareLock);
|
|
|
|
return ret;
|
|
} |