*
* ruleutils.c
* Functions to convert stored expressions/querytrees back to
* source text
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2021, openGauss Contributors
*
*
* IDENTIFICATION
* src/backend/utils/adt/ruleutils.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include <fcntl.h>
#ifndef WIN32
#include <sys/syscall.h>
#endif
#ifdef PGXC
#include "access/reloptions.h"
#endif
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#ifdef PGXC
#include "catalog/pg_aggregate.h"
#endif
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_fn.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_synonym.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/pg_set.h"
#include "catalog/pg_object.h"
#include "catalog/heap.h"
#include "catalog/gs_encrypted_proc.h"
#include "catalog/gs_encrypted_columns.h"
#include "catalog/gs_package.h"
#include "catalog/pg_proc_ext.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
#include "commands/tablecmds.h"
#include "executor/spi.h"
#include "executor/spi_priv.h"
#include "funcapi.h"
#include "miscadmin.h"
#ifdef PGXC
#include "nodes/execnodes.h"
#include "catalog/pgxc_class.h"
#endif
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes_common.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/keywords.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_hint.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "parser/parser.h"
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_utilcmd.h"
#include "parser/parse_relation.h"
#ifdef PGXC
#include "pgxc/pgxc.h"
#include "optimizer/pgxcplan.h"
#include "optimizer/pgxcship.h"
#endif
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/plpgsql.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/syscache.h"
#include "utils/snapmgr.h"
#include "utils/typcache.h"
#include "utils/xml.h"
#include "vecexecutor/vecnodes.h"
#include "db4ai/gd.h"
#include "commands/sqladvisor.h"
#include "commands/sequence.h"
#include "client_logic/client_logic.h"
* Pretty formatting constants
* ----------
*/
#define PRETTYINDENT_STD 8
#define PRETTYINDENT_JOIN 13
#define PRETTYINDENT_JOIN_ON (PRETTYINDENT_JOIN - PRETTYINDENT_STD)
#define PRETTYINDENT_VAR 4
#define PRETTYFLAG_PAREN 1
#define PRETTYFLAG_INDENT 2
#define GET_PRETTY_FLAGS(pretty) \
((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) \
: PRETTYFLAG_INDENT)
#define WRAP_COLUMN_DEFAULT 79
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
#define MAXFLOATWIDTH 64
#define MAXDOUBLEWIDTH 128
#define atooid(x) ((Oid)strtoul((x), NULL, 10))
#define MAXINT128LEN 45
#define UNIQUE_OFFSET 7
#define PRIMARY_KEY_OFFSET 11
* Local data types
* ----------
*/
typedef struct {
StringInfo buf;
List* namespaces;
List* windowClause;
List* windowTList;
unsigned int prettyFlags;
int wrapColumn;
int indentLevel;
bool varprefix;
#ifdef PGXC
bool finalise_aggs;
bool sortgroup_colno;
void* parser_arg;
#endif
bool qrw_phase;
bool viewdef;
bool is_fqs;
bool is_upsert_clause;
bool skip_lock;
} deparse_context;
* Each level of query context around a subtree needs a level of Var namespace.
* A Var having varlevelsup=N refers to the N'th item (counting from 0) in
* the current context's namespaces list.
*
* The rangetable is the list of actual RTEs from the query tree, and the
* cte list is the list of actual CTEs.
*
* When deparsing plan trees, there is always just a single item in the
* deparse_namespace list (since a plan tree never contains Vars with
* varlevelsup > 0). We store the PlanState node that is the immediate
* parent of the expression to be deparsed, as well as a list of that
* PlanState's ancestors. In addition, we store its outer and inner subplan
* state nodes, as well as their plan nodes' targetlists, and the indextlist
* if the current PlanState is an IndexOnlyScanState. (These fields could
* be derived on-the-fly from the current PlanState, but it seems notationally
* clearer to set them up as separate fields.)
*/
typedef struct {
List* rtable;
List* ctes;
PlanState* planstate;
List* ancestors;
PlanState* outer_planstate;
PlanState* inner_planstate;
List* outer_tlist;
List* inner_tlist;
List* index_tlist;
#ifdef PGXC
bool remotequery;
#endif
} deparse_namespace;
* Global data
* ----------
*/
static const char* query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
static const char* query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
typedef struct tableInfo {
int1 relcmpr;
char relkind;
char relpersistence;
char parttype;
bool relrowmovement;
bool hasindex;
bool hasPartialClusterKey;
Oid tablespace;
Oid spcid;
char* reloptions;
char* relname;
AttrNumber autoinc_attnum;
Oid autoinc_consoid;
Oid autoinc_seqoid;
Oid collate;
} tableInfo;
typedef struct SubpartitionInfo {
bool issubpartition;
char subparttype;
Oid subparentid;
Oid subpartkeytype;
AttrNumber attnum;
bool istypestring;
} SubpartitionInfo;
* Local functions
*
* Most of these functions used to use fixed-size buffers to build their
* results. Now, they take an (already initialized) StringInfo object
* as a parameter, and append their text output to its contents.
* ----------
*/
static char* deparse_expression_pretty(
Node* expr, List* dpcontext, bool forceprefix, bool showimplicit, int prettyFlags, int startIndent,
bool no_alias = false);
extern char* pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn);
extern char* pg_get_functiondef_worker(Oid funcid, int* headerlines);
extern char* pg_get_trigger_whenclause(Form_pg_trigger trigrec,Node* whenClause, bool pretty);
static char* pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId, StringInfo buf);
static char* pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char* pg_get_indexdef_worker(Oid indexrelid, int colno, const Oid* excludeOps, bool attrsOnly, bool showTblSpc,
int prettyFlags, bool dumpSchemaOnly = false, bool showPartitionLocal = true, bool showSubpartitionLocal = true);
void pg_get_indexdef_partitions(Oid indexrelid, Form_pg_index idxrec, bool showTblSpc, StringInfoData *buf,
bool dumpSchemaOnly, bool showPartitionLocal, bool showSubpartitionLocal);
static char* pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags, bool with_option=false);
static text* pg_get_expr_worker(text* expr, Oid relid, const char* relname, int prettyFlags);
static int print_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults);
static void print_function_ora_arguments(StringInfo buf, HeapTuple proctup);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_parallel_enable(StringInfo buf, HeapTuple procTup, int2 parallelCursorSeq, Oid funcid);
static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps);
#ifdef PGXC
static void set_deparse_plan(deparse_namespace* dpns, Plan* plan);
#endif
static void push_child_plan(deparse_namespace* dpns, PlanState* ps, deparse_namespace* save_dpns);
static void pop_child_plan(deparse_namespace* dpns, deparse_namespace* save_dpns);
static void push_ancestor_plan(deparse_namespace* dpns, ListCell* ancestor_cell, deparse_namespace* save_dpns);
static void pop_ancestor_plan(deparse_namespace* dpns, deparse_namespace* save_dpns);
static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags);
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags, int wrapColumn);
static void get_values_def(List* values_lists, deparse_context* context);
static void get_with_clause(Query* query, deparse_context* context);
static void get_select_query_def(Query* query, deparse_context* context, TupleDesc resultDesc);
static void get_insert_query_def(Query* query, deparse_context* context);
static void get_update_query_def(Query* query, deparse_context* context);
static void get_update_query_targetlist_def(
Query* query, List* targetList, RangeTblEntry* rte, deparse_context* context);
static void get_delete_query_def(Query* query, deparse_context* context);
static void get_utility_query_def(Query* query, deparse_context* context);
static void get_basic_select_query(Query* query, deparse_context* context, TupleDesc resultDesc);
static void get_target_list(Query* query, List* targetList, deparse_context* context, TupleDesc resultDesc);
static void get_setop_query(Node* setOp, Query* query, deparse_context* context, TupleDesc resultDesc);
static Node* get_rule_sortgroupclause(Index ref, List* tlist, bool force_colno, deparse_context* context);
#ifdef USE_SPQ
static Node* get_rule_sortgroupclause_spq(Index ref, bool force_colno, deparse_context* context);
#endif
static void get_rule_groupingset(GroupingSet* gset, List* targetlist, deparse_context* context);
static void get_rule_orderby(List* orderList, List* targetList, bool force_colno, deparse_context* context);
static void get_rule_windowclause(Query* query, deparse_context* context);
static void get_rule_windowspec(WindowClause* wc, List* targetList, deparse_context* context);
static void get_rule_windowspec_listagg(WindowClause* wc, List* targetList, deparse_context* context);
static char* get_variable(
Var* var, int levelsup, bool istoplevel, deparse_context* context, bool for_star = false, bool no_alias = false);
static RangeTblEntry* find_rte_by_refname(const char* refname, deparse_context* context);
static Node* find_param_referent(
Param* param, deparse_context* context, deparse_namespace** dpns_p, ListCell** ancestor_cell_p);
static void get_parameter(Param* param, deparse_context* context);
static const char* get_simple_binary_op_name(OpExpr* expr);
static bool isSimpleNode(Node* node, Node* parentNode, int prettyFlags);
static void appendContextKeyword(
deparse_context* context, const char* str, int indentBefore, int indentAfter, int indentPlus);
static void get_rule_expr(Node* node, deparse_context* context, bool showimplicit, bool no_alias = false);
static void get_rule_expr_funccall(Node* node, deparse_context* context, bool showimplicit);
static bool looks_like_function(Node* node);
static void get_oper_expr(OpExpr* expr, deparse_context* context, bool no_alias = false);
static void get_func_expr(FuncExpr* expr, deparse_context* context, bool showimplicit);
static void get_agg_expr(Aggref* aggref, deparse_context* context);
static void get_windowfunc_expr(WindowFunc* wfunc, deparse_context* context);
static void get_coercion_expr(
Node* arg, deparse_context* context, Oid resulttype, int32 resulttypmod, Node* parentNode);
static void get_const_expr(Const* constval, deparse_context* context, int showtype);
static void get_const_collation(Const* constval, deparse_context* context);
static void simple_quote_literal(StringInfo buf, const char* val);
static void get_sublink_expr(SubLink* sublink, deparse_context* context);
static void get_from_clause(Query* query, const char* prefix, deparse_context* context, List* fromlist = NIL,
bool isNeedError = true);
static void get_from_clause_item(Node* jtnode, Query* query, deparse_context* context, bool isNeedError = true);
static void get_from_clause_partition(RangeTblEntry* rte, StringInfo buf, deparse_context* context);
static void get_from_clause_subpartition(RangeTblEntry* rte, StringInfo buf, deparse_context* context);
static void get_from_clause_bucket(RangeTblEntry* rte, StringInfo buf, deparse_context* context);
static void get_from_clause_alias(Alias* alias, RangeTblEntry* rte, deparse_context* context);
static void get_from_clause_coldeflist(
List* names, List* types, List* typmods, List* collations, deparse_context* context);
static void get_tablesample_def(TableSampleClause* tablesample, deparse_context* context);
static void GetTimecapsuleDef(const TimeCapsuleClause* timeCapsule, deparse_context* context);
void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf);
static Node* processIndirection(Node* node, deparse_context* context, bool printit);
static void printSubscripts(ArrayRef* aref, deparse_context* context);
static char* get_relation_name(Oid relid, bool isNeedError = true);
static char* generate_relation_name(Oid relid, List* namespaces, bool isNeedError = true);
static char* generate_function_name(
Oid funcid, int nargs, List* argnames, Oid* argtypes, bool was_variadic, bool* use_variadic_p,
bool searchDepend = false);
static char* generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text* string_to_text(char* str);
static Oid SearchSysTable(const char* query);
static void replace_cl_types_in_argtypes(Oid func_id, int numargs, Oid* argtypes, bool *is_client_logic);
static void AppendSubPartitionByInfo(StringInfo buf, Oid tableoid, SubpartitionInfo *subpartinfo, tableInfo* tableinfo = NULL);
static void AppendSubPartitionDetail(StringInfo buf, tableInfo tableinfo, SubpartitionInfo *subpartinfo);
static void AppendRangeIntervalPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo);
static void AppendListPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo);
static void AppendHashPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo);
static void AppendTablespaceInfo(const char *spcname, StringInfo buf, tableInfo tableinfo);
static inline bool IsTableVisible(Oid tableoid);
static void get_table_partitiondef(StringInfo query, StringInfo buf, Oid tableoid, tableInfo tableinfo);
Var* get_var_from_node(Node* node, bool (*func)(Oid) = func_oid_check_reject);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
* get_ruledef - Do it all and return a text
* that could be used as a statement
* to recreate the rule
* ----------
*/
Datum pg_get_ruledef(PG_FUNCTION_ARGS)
{
Oid ruleoid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, 0)));
}
Datum pg_get_ruledef_ext(PG_FUNCTION_ARGS)
{
Oid ruleoid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
}
static char* pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
{
Datum args[1];
char nulls[1];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
* Do this first so that string is alloc'd in outer context not SPI's.
*/
initStringInfo(&buf);
* Connect to SPI manager
*/
SPI_STACK_LOG("connect", NULL, NULL);
if (SPI_connect() != SPI_OK_CONNECT)
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
* On the first call prepare the plan to lookup pg_rewrite. We read
* pg_rewrite over the SPI manager instead of using the syscache to be
* checked for read access on pg_rewrite.
*/
if (u_sess->cache_cxt.plan_getrulebyoid == NULL) {
Oid argtypes[1];
SPIPlanPtr plan;
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan == NULL)
ereport(ERROR,
(errcode(ERRCODE_SPI_PREPARE_FAILURE), errmsg("SPI_prepare failed for \"%s\"", query_getrulebyoid)));
Assert (u_sess->SPI_cxt._current->spi_hash_key == INVALID_SPI_KEY);
SPI_keepplan(plan);
u_sess->cache_cxt.plan_getrulebyoid = plan;
}
* Get the pg_rewrite tuple for this rule
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
spirc = SPI_execute_plan(u_sess->cache_cxt.plan_getrulebyoid, args, nulls, true, 1);
if (spirc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SPI_EXECUTE_FAILURE), errmsg("failed to get pg_rewrite tuple for rule %u", ruleoid)));
if (SPI_processed != 1)
appendStringInfo(&buf, "-");
else {
* Get the rule's definition and put it into executor's memory
*/
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
make_ruledef(&buf, ruletup, rulettc, prettyFlags);
}
* Disconnect from SPI manager
*/
SPI_STACK_LOG("finish", NULL, NULL);
if (SPI_finish() != SPI_OK_FINISH)
ereport(ERROR, (errcode(ERRCODE_SPI_FINISH_FAILURE), errmsg("SPI_finish failed")));
return buf.data;
}
static bool has_schema_privileges_of_viewoid(Oid viewoid)
{
Oid oid = GetNamespaceIdbyRelId(viewoid);
if (!(u_sess->analyze_cxt.is_under_analyze || (IS_PGXC_DATANODE && IsConnFromCoord()))) {
AclResult aclresult = pg_namespace_aclcheck(oid, GetUserId(), ACL_USAGE);
return aclresult == ACLCHECK_OK;
}
return true;
}
* get_viewdef - Mainly the same thing, but we
* only return the SELECT part of a view
* ----------
*/
Datum pg_get_viewdef(PG_FUNCTION_ARGS)
{
Oid viewoid = PG_GETARG_OID(0);
if (!has_schema_privileges_of_viewoid(viewoid)) {
PG_RETURN_NULL();
}
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0, -1)));
}
Datum pg_get_viewdef_ext(PG_FUNCTION_ARGS)
{
Oid viewoid = PG_GETARG_OID(0);
if (!has_schema_privileges_of_viewoid(viewoid)) {
PG_RETURN_NULL();
}
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
{
Oid viewoid = PG_GETARG_OID(0);
if (!has_schema_privileges_of_viewoid(viewoid)) {
PG_RETURN_NULL();
}
int wrap = PG_GETARG_INT32(1);
int prettyFlags;
char* result = NULL;
prettyFlags = PRETTYFLAG_PAREN | PRETTYFLAG_INDENT;
result = pg_get_viewdef_worker(viewoid, prettyFlags, wrap);
if (result == NULL) {
PG_RETURN_NULL();
}
PG_RETURN_TEXT_P(string_to_text(result));
}
Datum pg_get_viewdef_name(PG_FUNCTION_ARGS)
{
text* viewname = PG_GETARG_TEXT_P(0);
RangeVar* viewrel = NULL;
Oid viewoid;
List* names = NIL;
names = textToQualifiedNameList(viewname);
viewrel = makeRangeVarFromNameList(names);
viewoid = RangeVarGetRelid(viewrel, NoLock, false);
if (!has_schema_privileges_of_viewoid(viewoid)) {
PG_RETURN_NULL();
}
list_free_ext(names);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0, -1)));
}
Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
{
text* viewname = PG_GETARG_TEXT_P(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
RangeVar* viewrel = NULL;
Oid viewoid;
List* names = NIL;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
names = textToQualifiedNameList(viewname);
viewrel = makeRangeVarFromNameList(names);
viewoid = RangeVarGetRelid(viewrel, NoLock, false);
if (!has_schema_privileges_of_viewoid(viewoid)) {
PG_RETURN_NULL();
}
list_free_ext(names);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
* Common code for by-OID and by-name variants of pg_get_viewdef
*/
char* pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
{
Datum args[2];
char nulls[2];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
* Do this first so that string is alloc'd in outer context not SPI's.
*/
initStringInfo(&buf);
* Connect to SPI manager
*/
SPI_STACK_LOG("connect", NULL, NULL);
if (SPI_connect() != SPI_OK_CONNECT)
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
* On the first call prepare the plan to lookup pg_rewrite. We read
* pg_rewrite over the SPI manager instead of using the syscache to be
* checked for read access on pg_rewrite.
*/
if (u_sess->cache_cxt.plan_getviewrule == NULL) {
Oid argtypes[2];
SPIPlanPtr plan;
argtypes[0] = OIDOID;
argtypes[1] = NAMEOID;
plan = SPI_prepare(query_getviewrule, 2, argtypes);
if (plan == NULL)
ereport(ERROR,
(errcode(ERRCODE_SPI_PREPARE_FAILURE), errmsg("SPI_prepare failed for \"%s\"", query_getviewrule)));
Assert (u_sess->SPI_cxt._current->spi_hash_key == INVALID_SPI_KEY);
SPI_keepplan(plan);
u_sess->cache_cxt.plan_getviewrule = plan;
}
* Get the pg_rewrite tuple for the view's SELECT rule
*/
args[0] = ObjectIdGetDatum(viewoid);
args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
nulls[0] = ' ';
nulls[1] = ' ';
spirc = SPI_execute_plan(u_sess->cache_cxt.plan_getviewrule, args, nulls, true, 2);
if (spirc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SPI_EXECUTE_FAILURE), errmsg("failed to get pg_rewrite tuple for view %u", viewoid)));
if (SPI_processed != 1)
appendStringInfo(&buf, "Not a view");
else {
* Get the rule's definition and put it into executor's memory
*/
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
}
* Disconnect from SPI manager
*/
SPI_STACK_LOG("finish", NULL, NULL);
if (SPI_finish() != SPI_OK_FINISH)
ereport(ERROR, (errcode(ERRCODE_SPI_FINISH_FAILURE), errmsg("SPI_finish failed")));
return buf.data;
}
char* pg_get_viewdef_string(Oid viewid)
{
StringInfoData buf;
Relation pg_rewrite;
HeapTuple ruletup;
TupleDesc rulettc;
initStringInfo(&buf);
pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
ruletup = SearchSysCache2(RULERELNAME,
ObjectIdGetDatum(viewid),
PointerGetDatum(ViewSelectRuleName));
if (!HeapTupleIsValid(ruletup)) {
elog(ERROR, "cache lookup failed for rewrite rule for view with OID %u", viewid);
}
rulettc = pg_rewrite->rd_att;
make_viewdef(&buf, ruletup, rulettc, 0, WRAP_COLUMN_DEFAULT);
ReleaseSysCache(ruletup);
relation_close(pg_rewrite, AccessShareLock);
return buf.data;
}
* @Description: if the type is a string type
* @in typid - type oid
* @return - return true if the type is a string type.
*/
static bool isTypeString(Oid typid)
{
switch (typid) {
case INT2OID:
case INT4OID:
case INT8OID:
case FLOAT4OID:
case FLOAT8OID:
case NUMERICOID:
return false;
default:
return true;
}
}
static int GetDistributeKeyCount(Oid tableoid)
{
bool isnull;
HeapTuple tuple;
Datum datum;
int2vector* pcvec = NULL;
int count;
tuple = SearchSysCache1(PGXCCLASSRELID, ObjectIdGetDatum(tableoid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("table oid doesn't exist in system catalog table.")));
}
datum = SysCacheGetAttr(PGXCCLASSRELID, tuple, Anum_pgxc_class_pcattnum, &isnull);
if (isnull) {
ReleaseSysCache(tuple);
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("table's distribution key is null.")));
}
pcvec = (int2vector*)DatumGetPointer(datum);
count = pcvec->dim1;
ReleaseSysCache(tuple);
return count;
}
static char* GetSliceName(HeapTuple tuple, TupleDesc desc)
{
int fieldNumber;
char* fieldValue = NULL;
fieldNumber = SPI_fnumber(desc, "slice");
fieldValue = SPI_getvalue(tuple, desc, fieldNumber);
return fieldValue;
}
static void AppendSliceName(StringInfo buf, HeapTuple tuple, TupleDesc desc)
{
appendStringInfo(buf, "SLICE ");
appendStringInfo(buf, "%s", GetSliceName(tuple, desc));
return;
}
static char* GetSliceNodeName(HeapTuple tuple, TupleDesc desc)
{
int fieldNumber;
char* fieldValue = NULL;
fieldNumber = SPI_fnumber(desc, "nodename");
fieldValue = SPI_getvalue(tuple, desc, fieldNumber);
return fieldValue;
}
static void AppendSliceNodeName(StringInfo buf, const char* nodeName)
{
appendStringInfo(buf, ")");
if (nodeName != NULL) {
appendStringInfo(buf, " DATANODE ");
appendStringInfo(buf, "%s", nodeName);
}
return;
}
static void AppendSliceBoundary(StringInfo buf, HeapTuple tuple, TupleDesc desc,
Oid tableoid, const int* boundaryIdx, const int* keyAttrs, int keyNum, bool parentheses)
{
Oid fieldType;
char* fieldValue = NULL;
if (parentheses) {
appendStringInfo(buf, "(");
}
for (int i = 0; i < keyNum; i++) {
if (i > 0) {
appendStringInfo(buf, ", ");
}
fieldValue = SPI_getvalue(tuple, desc, boundaryIdx[i]);
if (fieldValue == NULL) {
appendStringInfo(buf, "MAXVALUE");
} else {
fieldType = get_atttype(tableoid, (AttrNumber)keyAttrs[i]);
if (isTypeString(fieldType)) {
appendStringInfo(buf, "'%s'", fieldValue);
} else {
appendStringInfo(buf, "%s", fieldValue);
}
}
}
if (parentheses) {
appendStringInfo(buf, ")");
}
return;
}
static int* GetBoundaryFieldNumber(TupleDesc desc, int keyNum)
{
int rc;
char fieldName[NAMEDATALEN];
int* boundaryIdx = (int*)palloc0(keyNum * sizeof(int));
for (int i = 1; i <= keyNum; i++) {
rc = snprintf_s(fieldName,
NAMEDATALEN,
NAMEDATALEN - 1,
"distboundary_%d",
i);
securec_check_ss_c(rc, "\0", "\0");
boundaryIdx[i - 1] = SPI_fnumber(desc, fieldName);
}
return boundaryIdx;
}
static int* GetDistributeKeyAttrNumber(Oid tableoid)
{
int i;
bool isnull;
HeapTuple tuple;
Datum datum;
int2vector* pcvec = NULL;
int* keyAttrNumber = NULL;
tuple = SearchSysCache1(PGXCCLASSRELID, ObjectIdGetDatum(tableoid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("table oid doesn't exist in system catalog table.")));
}
datum = SysCacheGetAttr(PGXCCLASSRELID, tuple, Anum_pgxc_class_pcattnum, &isnull);
pcvec = (int2vector*)DatumGetPointer(datum);
keyAttrNumber = (int*)palloc0(pcvec->dim1 * sizeof(int));
for (i = 0; i < pcvec->dim1; i++) {
keyAttrNumber[i] = pcvec->values[i];
}
ReleaseSysCache(tuple);
return keyAttrNumber;
}
static void GetRangeSliceDefs(StringInfo query, Oid tableoid, int keyNum)
{
int i;
resetStringInfo(query);
appendStringInfo(query, "select ");
for (i = 1; i <= keyNum; i++) {
appendStringInfo(query, "p.boundaries[%d] AS distboundary_%d, ", i, i);
}
appendStringInfo(query,
"p.relname as slice, case when p.specified = 't' then q.node_name else null end as nodename "
"FROM pgxc_slice p JOIN pgxc_node q on p.nodeoid = q.oid "
"WHERE p.relid = '%u' AND p.type = 's' ORDER BY p.sliceorder",
tableoid);
if (SPI_execute(query->data, true, INT_MAX) != SPI_OK_SELECT) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("fail to execute query")));
}
return;
}
static void GetRangeDistributionDef(StringInfo query, StringInfo buf, Oid tableoid)
{
int i;
int tuplenum;
int keyNum;
HeapTuple tuple;
TupleDesc desc;
int* boundaryIdx = NULL;
int* keyAttrs = NULL;
char* nodeName = NULL;
keyNum = GetDistributeKeyCount(tableoid);
keyAttrs = GetDistributeKeyAttrNumber(tableoid);
GetRangeSliceDefs(query, tableoid, keyNum);
boundaryIdx = GetBoundaryFieldNumber(SPI_tuptable->tupdesc, keyNum);
tuplenum = SPI_processed;
appendStringInfo(buf, "\n(\n ");
for (i = 0; i < tuplenum; i++) {
tuple = SPI_tuptable->vals[i];
desc = SPI_tuptable->tupdesc;
nodeName = GetSliceNodeName(tuple, desc);
AppendSliceName(buf, tuple, desc);
appendStringInfo(buf, " VALUES LESS THAN (");
AppendSliceBoundary(buf, tuple, desc, tableoid, boundaryIdx, keyAttrs, keyNum, false);
AppendSliceNodeName(buf, nodeName);
if (i < tuplenum - 1) {
appendStringInfo(buf, ",\n ");
}
}
appendStringInfo(buf, "\n)");
pfree_ext(boundaryIdx);
pfree_ext(keyAttrs);
return;
}
static void GetListSliceDefs(StringInfo query, Oid tableoid, int keyNum)
{
int i;
resetStringInfo(query);
appendStringInfo(query, "select ");
for (i = 1; i <= keyNum; i++) {
appendStringInfo(query, "p.boundaries[%d] AS distboundary_%d, ", i, i);
}
appendStringInfo(query,
"p.relname as slice, p.sindex as sindex, "
"case when p.specified = 't' then q.node_name else null end as nodename "
"FROM pgxc_slice p JOIN pgxc_node q ON p.nodeoid = q.oid "
"WHERE p.relid = '%u' AND p.type = 's' ORDER BY p.sliceorder, p.sindex",
tableoid);
if (SPI_execute(query->data, true, INT_MAX) != SPI_OK_SELECT) {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("fail to execute query")));
}
return;
}
static int GetSlicesindex(HeapTuple tuple, TupleDesc desc)
{
int result = 0;
int fieldNumber;
char* fieldValue = NULL;
fieldNumber = SPI_fnumber(desc, "sindex");
fieldValue = SPI_getvalue(tuple, desc, fieldNumber);
if (fieldValue != NULL) {
result = atoi(fieldValue);
}
return result;
}
static void GetListDistributionDef(StringInfo query, StringInfo buf, Oid tableoid)
{
int i;
int keyNum;
int tuplenum;
int sindex;
bool parentheses;
char* nodeName = NULL;
int* boundaryIdx = NULL;
int* keyAttrs = NULL;
HeapTuple tuple;
TupleDesc desc;
keyNum = GetDistributeKeyCount(tableoid);
keyAttrs = GetDistributeKeyAttrNumber(tableoid);
parentheses = (keyNum > 1);
GetListSliceDefs(query, tableoid, keyNum);
boundaryIdx = GetBoundaryFieldNumber(SPI_tuptable->tupdesc, keyNum);
tuplenum = SPI_processed;
appendStringInfo(buf, "\n(\n");
for (i = 0; i < tuplenum; i++) {
tuple = SPI_tuptable->vals[i];
desc = SPI_tuptable->tupdesc;
sindex = GetSlicesindex(tuple, desc);
if (sindex == 0) {
if (i != 0) {
AppendSliceNodeName(buf, nodeName);
appendStringInfo(buf, ",\n");
}
appendStringInfo(buf, " ");
AppendSliceName(buf, tuple, desc);
appendStringInfo(buf, " VALUES (");
pfree_ext(nodeName);
nodeName = pstrdup_ext(GetSliceNodeName(tuple, desc));
} else {
appendStringInfo(buf, ", ");
}
if (SPI_getvalue(tuple, desc, boundaryIdx[0]) == NULL) {
appendStringInfo(buf, "DEFAULT");
} else {
AppendSliceBoundary(buf, tuple, desc, tableoid, boundaryIdx, keyAttrs, keyNum, parentheses);
}
}
AppendSliceNodeName(buf, nodeName);
appendStringInfo(buf, "\n)");
pfree_ext(boundaryIdx);
pfree_ext(keyAttrs);
pfree_ext(nodeName);
return;
}
void GetPartitionExprKeySrc(StringInfo buf, Datum* datum, char* relname, Oid tableoid, int* partkeynum, Oid** iPartboundary)
{
*partkeynum = 1;
*iPartboundary = (Oid*)palloc0(*partkeynum * sizeof(Oid));
char* partkeystr = MemoryContextStrdup(LocalMyDBCacheMemCxt(), TextDatumGetCString(*datum));
Node* partkeyexpr = NULL;
if (partkeystr)
partkeyexpr = (Node*)stringToNode_skip_extern_fields(partkeystr);
else
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("fata error: The partkeystr can't be NULL")));
if (!partkeyexpr)
ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The partkeyexpr can't be NULL")));
if (partkeyexpr->type == T_OpExpr)
(*iPartboundary)[0] = ((OpExpr*)partkeyexpr)->opresulttype;
else if (partkeyexpr->type == T_FuncExpr)
(*iPartboundary)[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)));
char* partKeyExprSrc = deparse_expression(partkeyexpr, deparse_context_for(relname, tableoid), false, false);
appendStringInfo(buf, "%s", partKeyExprSrc);
pfree_ext(partKeyExprSrc);
pfree_ext(partkeystr);
}
char *pg_get_partkeydef_string(Relation relation)
{
OverrideSearchPath *tmp_search_path = NULL;
StringInfoData buf;
StringInfoData query;
tableInfo tableinfo;
Form_pg_class classForm = NULL;
Oid tableoid = RelationGetRelid(relation);
if (IsTempTable(tableoid)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Can not get temporary tables partition defination.")));
}
initStringInfo(&buf);
initStringInfo(&query);
classForm = relation->rd_rel;
tableinfo.relkind = classForm->relkind;
if (tableinfo.relkind != RELKIND_RELATION) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Not a ordinary table or foreign table.")));
}
tableinfo.relpersistence = classForm->relpersistence;
tableinfo.tablespace = classForm->reltablespace;
tableinfo.hasPartialClusterKey = classForm->relhasclusterkey;
tableinfo.hasindex = classForm->relhasindex;
tableinfo.relcmpr = classForm->relcmprs;
tableinfo.relrowmovement = classForm->relrowmovement;
tableinfo.parttype = classForm->parttype;
tableinfo.spcid = classForm->relnamespace;
tableinfo.relname = pstrdup(NameStr(classForm->relname));
tableinfo.autoinc_attnum = 0;
tableinfo.autoinc_consoid = 0;
tableinfo.autoinc_seqoid = 0;
tmp_search_path = GetOverrideSearchPath(CurrentMemoryContext);
tmp_search_path->schemas = NIL;
tmp_search_path->addCatalog = true;
tmp_search_path->addTemp = true;
PushOverrideSearchPath(tmp_search_path);
* Connect to SPI manager
*/
SPI_STACK_LOG("connect", NULL, NULL);
if (SPI_connect() != SPI_OK_CONNECT)
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE),
errmsg("SPI_connect failed")));
PushActiveSnapshot(GetTransactionSnapshot());
get_table_partitiondef(&query, &buf, tableoid, tableinfo);
PopActiveSnapshot();
* Disconnect from SPI manager
*/
SPI_STACK_LOG("finish", NULL, NULL);
if (SPI_finish() != SPI_OK_FINISH)
ereport(ERROR, (errcode(ERRCODE_SPI_FINISH_FAILURE),
errmsg("SPI_finish failed")));
PopOverrideSearchPath();
pfree_ext(query.data);
pfree_ext(tableinfo.relname);
return buf.data;
}
* @Description: get partition table defination
* @in query - append query for SPI_execute.
* @in buf - append defination string for partition table.
* @in tableoid - parent table oid.
* @in tableinfo - parent table info.
* @return - void
*/
static void get_table_partitiondef(StringInfo query, StringInfo buf, Oid tableoid, tableInfo tableinfo)
{
bool isnull = false;
bool isPartExprKeyNull = false;
Relation relation = NULL;
ScanKeyData key[2];
SysScanDesc scan = NULL;
HeapTuple tuple = NULL;
char relkind = RELKIND_RELATION;
char partstrategy = PART_STRATEGY_VALUE;
char parttype = PARTTYPE_NON_PARTITIONED_RELATION;
int partkeynum = 0;
Oid* iPartboundary = NULL;
Form_pg_partition partition = NULL;
HeapTuple ctuple = SearchSysCache1(RELOID, ObjectIdGetDatum(tableoid));
if (!HeapTupleIsValid(ctuple)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relid %u", tableoid)));
}
Form_pg_class reltuple = (Form_pg_class)GETSTRUCT(ctuple);
parttype = reltuple->parttype;
ReleaseSysCache(ctuple);
if (parttype == PARTTYPE_NON_PARTITIONED_RELATION) {
return;
}
relation = heap_open(PartitionRelationId, AccessShareLock);
ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(relkind));
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(relation, PartitionParentOidIndexId, true, NULL, 2, key);
if (HeapTupleIsValid(tuple = systable_getnext(scan))) {
int2vector* partVec = NULL;
Datum datum = 0;
datum = SysCacheGetAttr(PARTRELID, tuple, Anum_pg_partition_partkeyexpr, &isPartExprKeyNull);
if (isPartExprKeyNull)
datum = SysCacheGetAttr(PARTRELID, tuple, Anum_pg_partition_partkey, &isnull);
partition = (Form_pg_partition)GETSTRUCT(tuple);
appendStringInfo(buf, "\n");
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE || tableinfo.relkind == RELKIND_STREAM) {
appendStringInfo(buf, "PARTITION BY (");
} else {
partstrategy = partition->partstrategy;
switch (partition->partstrategy) {
case PART_STRATEGY_RANGE:
case PART_STRATEGY_INTERVAL:
appendStringInfo(buf, "PARTITION BY RANGE (");
break;
case PART_STRATEGY_LIST:
appendStringInfo(buf, "PARTITION BY LIST (");
break;
case PART_STRATEGY_HASH:
appendStringInfo(buf, "PARTITION BY HASH (");
break;
case PART_STRATEGY_VALUE:
appendStringInfo(buf, "PARTITION BY VALUES (");
break;
default:
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(
"unrecognized partition type %c for table %s", partition->partstrategy, tableinfo.relname)));
}
}
if (isnull == false && isPartExprKeyNull) {
partVec = (int2vector*)DatumGetPointer(datum);
partkeynum = partVec->dim1;
iPartboundary = (Oid*)palloc0(partkeynum * sizeof(Oid));
bool firstFlag = true;
for (int i = 0; i < partVec->dim1; i++) {
char* attname = get_attname(tableoid, partVec->values[i]);
iPartboundary[i] = get_atttype(tableoid, partVec->values[i]);
if (!firstFlag) {
appendStringInfo(buf, ", ");
}
firstFlag = false;
appendStringInfo(buf, "%s", quote_identifier(attname));
pfree_ext(attname);
}
} else if (!isPartExprKeyNull) {
GetPartitionExprKeySrc(buf, &datum, tableinfo.relname, tableoid, &partkeynum, &iPartboundary);
}
appendStringInfo(buf, ")");
}
systable_endscan(scan);
heap_close(relation, AccessShareLock);
if (partstrategy == PART_STRATEGY_INTERVAL) {
resetStringInfo(query);
if (!u_sess->attr.attr_sql.dolphin) {
appendStringInfo(query,
"SELECT p.interval[1] AS interval FROM pg_partition p "
"WHERE p.parentid = %u AND p.parttype = '%c' AND p.partstrategy = '%c'",
tableoid, PART_OBJ_TYPE_PARTED_TABLE, PART_STRATEGY_INTERVAL);
} else {
appendStringInfo(query,
"SELECT p.`interval`[1] AS `interval` FROM pg_partition p "
"WHERE p.parentid = %u AND p.parttype = '%c' AND p.partstrategy = '%c'",
tableoid, PART_OBJ_TYPE_PARTED_TABLE, PART_STRATEGY_INTERVAL);
}
(void)SPI_execute(query->data, true, INT_MAX);
Assert(SPI_processed == 1);
HeapTuple spiTuple = SPI_tuptable->vals[0];
TupleDesc spiTupdesc = SPI_tuptable->tupdesc;
char *ivalue = SPI_getvalue(spiTuple, spiTupdesc, SPI_fnumber(spiTupdesc, "interval"));
appendStringInfo(buf, "\nINTERVAL ('%s')", ivalue);
}
SubpartitionInfo *subpartinfo = (SubpartitionInfo *)palloc0(sizeof(SubpartitionInfo));
if (parttype == PARTTYPE_SUBPARTITIONED_RELATION) {
AppendSubPartitionByInfo(buf, tableoid, subpartinfo, &tableinfo);
}
if (partstrategy == PART_STRATEGY_RANGE || partstrategy == PART_STRATEGY_INTERVAL) {
AppendRangeIntervalPartitionInfo(buf, tableoid, tableinfo, partkeynum, iPartboundary, subpartinfo);
} else if (partstrategy == PART_STRATEGY_LIST) {
AppendListPartitionInfo(buf, tableoid, tableinfo, partkeynum, iPartboundary, subpartinfo);
} else if (partstrategy == PART_STRATEGY_HASH) {
AppendHashPartitionInfo(buf, tableoid, tableinfo, partkeynum, iPartboundary, subpartinfo);
} else {
pfree_ext(iPartboundary);
pfree_ext(subpartinfo);
return;
}
if (tableinfo.relrowmovement) {
appendStringInfo(buf, "\n%s", "ENABLE ROW MOVEMENT");
}
pfree_ext(iPartboundary);
pfree_ext(subpartinfo);
}
static void AppendSubPartitionByInfo(StringInfo buf, Oid tableoid, SubpartitionInfo *subpartinfo, tableInfo* tableinfo)
{
Relation partrel = NULL;
ScanKeyData key[2];
SysScanDesc scan = NULL;
HeapTuple parttuple = NULL;
ScanKeyData subkey[2];
SysScanDesc subscan = NULL;
HeapTuple subparttuple = NULL;
bool isnull = false;
bool isPartExprKeyNull = false;
partrel = heap_open(PartitionRelationId, AccessShareLock);
ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(PARTTYPE_PARTITIONED_RELATION));
ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(partrel, PartitionParentOidIndexId, true, NULL, 2, key);
parttuple = systable_getnext(scan);
if (!HeapTupleIsValid(parttuple)) {
systable_endscan(scan);
heap_close(partrel, AccessShareLock);
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("could not find partition tuple for subpartition relation %u", tableoid)));
}
Datum datum = 0;
datum = SysCacheGetAttr(PARTRELID, parttuple, Anum_pg_partition_partkeyexpr, &isPartExprKeyNull);
if (isPartExprKeyNull)
datum = SysCacheGetAttr(PARTRELID, parttuple, Anum_pg_partition_partkey, &isnull);
Assert(!isnull || !isPartExprKeyNull);
Oid subparentid = HeapTupleGetOid(parttuple);
ScanKeyInit(&subkey[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(PARTTYPE_SUBPARTITIONED_RELATION));
ScanKeyInit(&subkey[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(subparentid));
subscan = systable_beginscan(partrel, PartitionParentOidIndexId, true, NULL, 2, subkey);
subparttuple = systable_getnext(subscan);
if (!HeapTupleIsValid(subparttuple)) {
systable_endscan(scan);
systable_endscan(subscan);
heap_close(partrel, AccessShareLock);
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("could not find subpartition tuple for subpartition relation %u", tableoid)));
}
Form_pg_partition part = (Form_pg_partition)GETSTRUCT(subparttuple);
switch (part->partstrategy) {
case PART_STRATEGY_RANGE:
appendStringInfo(buf, " SUBPARTITION BY RANGE (");
break;
case PART_STRATEGY_LIST:
appendStringInfo(buf, " SUBPARTITION BY LIST (");
break;
case PART_STRATEGY_HASH:
appendStringInfo(buf, " SUBPARTITION BY HASH (");
break;
default:
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized subpartition type %c", part->partstrategy)));
}
int partkeynum = 0;
if (!isnull && isPartExprKeyNull) {
int2vector *partVec = (int2vector *)DatumGetPointer(datum);
partkeynum = partVec->dim1;
char *attname = get_attname(tableoid, partVec->values[0]);
appendStringInfo(buf, "%s", quote_identifier(attname));
pfree_ext(attname);
subpartinfo->attnum = partVec->values[0];
subpartinfo->subpartkeytype = get_atttype(tableoid, subpartinfo->attnum);
} else if (!isPartExprKeyNull) {
Oid* iPartboundary = NULL;
GetPartitionExprKeySrc(buf, &datum, tableinfo->relname, tableoid, &partkeynum, &iPartboundary);
subpartinfo->subpartkeytype = *iPartboundary;
pfree_ext(iPartboundary);
}
appendStringInfo(buf, ")");
if (partkeynum != 1) {
systable_endscan(scan);
heap_close(partrel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only support one partkey in subpartition table")));
}
subpartinfo->issubpartition = true;
subpartinfo->subparttype = part->partstrategy;
subpartinfo->istypestring = isTypeString(subpartinfo->subpartkeytype);
systable_endscan(scan);
systable_endscan(subscan);
heap_close(partrel, AccessShareLock);
}
static void AppendSubPartitionDetail(StringInfo buf, tableInfo tableinfo, SubpartitionInfo *subpartinfo)
{
appendStringInfo(buf, "\n (");
StringInfo query = makeStringInfo();
appendStringInfo(query,
"SELECT /*+ hashjoin(p t) */ p.relname AS partName, "
"array_to_string(p.boundaries, ',') as partbound, "
"array_to_string(p.boundaries, ''',''') as partboundstr, "
"t.spcname AS reltblspc "
"FROM pg_catalog.pg_partition p LEFT JOIN pg_catalog.pg_tablespace t "
"ON p.reltablespace = t.oid "
"WHERE p.parentid = %u AND p.parttype = '%c' AND p.partstrategy = '%c' "
"ORDER BY p.boundaries[1]::%s ASC NULLS LAST",
subpartinfo->subparentid, PART_OBJ_TYPE_TABLE_SUB_PARTITION, subpartinfo->subparttype,
get_typename(subpartinfo->subpartkeytype));
(void)SPI_execute(query->data, true, INT_MAX);
int proc = SPI_processed;
SPITupleTable *spitup = SPI_tuptable;
for (int i = 0; i < proc; i++) {
if (i > 0) {
appendStringInfo(buf, ",");
}
HeapTuple spi_tuple = spitup->vals[i];
TupleDesc spi_tupdesc = spitup->tupdesc;
char *pname = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partname"));
appendStringInfo(buf, "\n SUBPARTITION %s", quote_identifier(pname));
if (subpartinfo->subparttype == PART_STRATEGY_RANGE) {
appendStringInfo(buf, " VALUES LESS THAN (");
char *pvalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partbound"));
if (pvalue == NULL || strlen(pvalue) == 0) {
appendStringInfo(buf, "MAXVALUE");
} else if (subpartinfo->istypestring) {
char *svalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partboundstr"));
appendStringInfo(buf, "'%s'", svalue);
pfree_ext(svalue);
} else {
appendStringInfo(buf, "%s", pvalue);
}
appendStringInfo(buf, ")");
pfree_ext(pvalue);
} else if (subpartinfo->subparttype == PART_STRATEGY_LIST) {
appendStringInfo(buf, " VALUES (");
char *pvalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partbound"));
if (pvalue == NULL || strlen(pvalue) == 0) {
appendStringInfo(buf, "DEFAULT");
} else if (subpartinfo->istypestring) {
char *svalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partboundstr"));
appendStringInfo(buf, "'%s'", svalue);
pfree_ext(svalue);
} else {
appendStringInfo(buf, "%s", pvalue);
}
appendStringInfo(buf, ")");
pfree_ext(pvalue);
}
* Append partition tablespace.
* Skip it, if partition tablespace is the same as partitioned table.
*/
int fno = SPI_fnumber(spi_tupdesc, "reltblspc");
const char *spcname = SPI_getvalue(spi_tuple, spi_tupdesc, fno);
AppendTablespaceInfo(spcname, buf, tableinfo);
}
DestroyStringInfo(query);
appendStringInfo(buf, "\n )");
}
static void AppendRangeIntervalPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo)
{
appendStringInfo(buf, "\n( ");
StringInfo query = makeStringInfo();
appendStringInfo(query, "SELECT /*+ hashjoin(p t) */p.relname AS partname, ");
for (int i = 1; i <= partkeynum; i++) {
appendStringInfo(query, "p.boundaries[%d] AS partboundary_%d, ", i, i);
}
appendStringInfo(query,
"p.oid AS partoid, "
"t.spcname AS reltblspc "
"FROM pg_catalog.pg_partition p LEFT JOIN pg_catalog.pg_tablespace t "
"ON p.reltablespace = t.oid "
"WHERE p.parentid = %u AND p.parttype = '%c' "
"AND p.partstrategy = '%c' ORDER BY ",
tableoid, PART_OBJ_TYPE_TABLE_PARTITION, PART_STRATEGY_RANGE);
for (int i = 1; i <= partkeynum; i++) {
if (i == partkeynum) {
appendStringInfo(query, "p.boundaries[%d]::%s ASC NULLS LAST", i, get_typename(iPartboundary[i - 1]));
} else {
appendStringInfo(query, "p.boundaries[%d]::%s NULLS LAST, ", i, get_typename(iPartboundary[i - 1]));
}
}
(void)SPI_execute(query->data, true, INT_MAX);
int proc = SPI_processed;
SPITupleTable *spitup = SPI_tuptable;
for (int i = 0; i < proc; i++) {
if (i > 0) {
appendStringInfo(buf, ",");
}
HeapTuple spi_tuple = spitup->vals[i];
TupleDesc spi_tupdesc = spitup->tupdesc;
char *pname = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partname"));
appendStringInfo(buf, "\n PARTITION %s VALUES LESS THAN (", quote_identifier(pname));
for (int j = 0; j < partkeynum; j++) {
if (j > 0) {
appendStringInfo(buf, ", ");
}
char *pvalue = NULL;
char checkRowName[32] = {0};
int rowNameLen = sizeof(checkRowName);
int nRet = 0;
nRet = snprintf_s(checkRowName, rowNameLen, rowNameLen - 1, "partboundary_%d", j + 1);
securec_check_ss(nRet, "\0", "\0");
pvalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, checkRowName));
if (pvalue == NULL) {
appendStringInfo(buf, "MAXVALUE");
continue;
} else {
if (isTypeString(iPartboundary[j])) {
appendStringInfo(buf, "'%s'", pvalue);
} else {
appendStringInfo(buf, "%s", pvalue);
}
}
}
appendStringInfo(buf, ")");
* Append partition tablespace.
* Skip it, if partition tablespace is the same as partitioned table.
*/
int fno = SPI_fnumber(spi_tupdesc, "reltblspc");
const char *spcname = SPI_getvalue(spi_tuple, spi_tupdesc, fno);
AppendTablespaceInfo(spcname, buf, tableinfo);
if (subpartinfo->issubpartition) {
subpartinfo->subparentid =
atooid(SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partoid")));
AppendSubPartitionDetail(buf, tableinfo, subpartinfo);
}
}
DestroyStringInfo(query);
appendStringInfo(buf, "\n)");
}
static void AppendListPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo)
{
appendStringInfo(buf, "\n( ");
StringInfo query = makeStringInfo();
if (partkeynum == 1) {
appendStringInfo(query,
"SELECT /*+ hashjoin(p t) */p.relname AS partname, "
"array_to_string(p.boundaries, ',') as partbound, "
"array_to_string(p.boundaries, ''',''') as partboundstr, "
"p.oid AS partoid, "
"t.spcname AS reltblspc "
"FROM pg_catalog.pg_partition p LEFT JOIN pg_catalog.pg_tablespace t "
"ON p.reltablespace = t.oid "
"WHERE p.parentid = %u AND p.parttype = '%c' "
"AND p.partstrategy = '%c' ORDER BY p.boundaries[1]::%s ASC NULLS LAST",
tableoid, PART_OBJ_TYPE_TABLE_PARTITION, PART_STRATEGY_LIST, get_typename(*iPartboundary));
} else {
appendStringInfo(query,
"SELECT /*+ hashjoin(p t) */p.relname AS partname, "
"p.bound_def AS partbound, "
"p.oid AS partoid, "
"t.spcname AS reltblspc FROM ( "
"SELECT oid, relname, reltablespace, pg_catalog.string_agg(bound,',' ORDER BY bound_id NULLS LAST) AS bound_def FROM( "
"SELECT oid, relname, reltablespace, bound_id, '('||"
"pg_catalog.array_to_string(pg_catalog.array_agg(key_value ORDER BY key_id), ',', 'NULL')||')' AS bound "
"FROM ( SELECT oid, relname, reltablespace, bound_id, key_id, ");
int cnt = 0;
for (int i = 0; i < partkeynum; i++) {
if (!isTypeString(iPartboundary[i])) {
continue;
}
if (cnt > 0) {
appendStringInfo(query, ",");
} else {
appendStringInfo(query, "CASE WHEN key_id in (");
}
appendStringInfo(query, "%d", i + 1);
cnt++;
}
if (cnt > 0) {
appendStringInfo(query, ") THEN pg_catalog.quote_literal(key_value) ELSE key_value END AS ");
}
appendStringInfo(query,
"key_value FROM ( "
"SELECT oid, relname, reltablespace, bound_id, pg_catalog.generate_subscripts(keys_array, 1) AS key_id, "
"pg_catalog.unnest(keys_array)::text AS key_value FROM ( "
"SELECT oid, relname, reltablespace, bound_id,key_bounds::cstring[] AS keys_array FROM ( "
"SELECT oid, relname, reltablespace, pg_catalog.unnest(boundaries) AS key_bounds, "
"pg_catalog.generate_subscripts(boundaries, 1) AS bound_id FROM pg_catalog.pg_partition "
"WHERE parentid = %u AND parttype = '%c' AND partstrategy = '%c')))) "
"GROUP BY oid, relname, reltablespace, bound_id) "
"GROUP BY oid, relname, reltablespace "
"UNION ALL SELECT oid, relname, reltablespace, 'DEFAULT' AS bound_def FROM pg_catalog.pg_partition "
"WHERE parentid = %u AND parttype = '%c' AND partstrategy = '%c' AND boundaries[1] IS NULL) p "
"LEFT JOIN pg_catalog.pg_tablespace t ON p.reltablespace = t.oid "
"ORDER BY p.bound_def ASC NULLS LAST",
tableoid, PART_OBJ_TYPE_TABLE_PARTITION, PART_STRATEGY_LIST,
tableoid, PART_OBJ_TYPE_TABLE_PARTITION, PART_STRATEGY_LIST);
}
(void)SPI_execute(query->data, true, INT_MAX);
int proc = SPI_processed;
SPITupleTable *spitup = SPI_tuptable;
for (int i = 0; i < proc; i++) {
if (i > 0) {
appendStringInfo(buf, ",");
}
HeapTuple spi_tuple = spitup->vals[i];
TupleDesc spi_tupdesc = spitup->tupdesc;
char *pname = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partname"));
appendStringInfo(buf, "\n PARTITION %s VALUES (", quote_identifier(pname));
char *pvalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partbound"));
if (pvalue == NULL || strlen(pvalue) == 0) {
appendStringInfo(buf, "DEFAULT");
} else if (partkeynum == 1 && isTypeString(*iPartboundary)) {
char *svalue = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partboundstr"));
appendStringInfo(buf, "'%s'", svalue);
pfree_ext(svalue);
} else {
appendStringInfo(buf, "%s", pvalue);
}
appendStringInfo(buf, ")");
pfree_ext(pvalue);
* Append partition tablespace.
* Skip it, if partition tablespace is the same as partitioned table.
*/
int fno = SPI_fnumber(spi_tupdesc, "reltblspc");
const char *spcname = SPI_getvalue(spi_tuple, spi_tupdesc, fno);
AppendTablespaceInfo(spcname, buf, tableinfo);
if (subpartinfo->issubpartition) {
subpartinfo->subparentid =
atooid(SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partoid")));
AppendSubPartitionDetail(buf, tableinfo, subpartinfo);
}
}
DestroyStringInfo(query);
appendStringInfo(buf, "\n)");
}
static void AppendHashPartitionInfo(StringInfo buf, Oid tableoid, tableInfo tableinfo, int partkeynum,
Oid *iPartboundary, SubpartitionInfo *subpartinfo)
{
appendStringInfo(buf, "\n( ");
Assert(partkeynum == 1);
StringInfo query = makeStringInfo();
appendStringInfo(query,
"SELECT /*+ hashjoin(p t) */p.relname AS partname, "
"p.boundaries[1] AS partboundary, "
"p.oid AS partoid, "
"t.spcname AS reltblspc "
"FROM pg_catalog.pg_partition p LEFT JOIN pg_catalog.pg_tablespace t "
"ON p.reltablespace = t.oid "
"WHERE p.parentid = %u AND p.parttype = '%c' "
"AND p.partstrategy = '%c' ORDER BY ",
tableoid, PART_OBJ_TYPE_TABLE_PARTITION, PART_STRATEGY_HASH);
appendStringInfo(query, "p.boundaries[1]::%s ASC NULLS LAST", get_typename(*iPartboundary));
(void)SPI_execute(query->data, true, INT_MAX);
int proc = SPI_processed;
SPITupleTable *spitup = SPI_tuptable;
for (int i = 0; i < proc; i++) {
if (i > 0) {
appendStringInfo(buf, ",");
}
HeapTuple spi_tuple = spitup->vals[i];
TupleDesc spi_tupdesc = spitup->tupdesc;
char *pname = SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partname"));
appendStringInfo(buf, "\n PARTITION %s", quote_identifier(pname));
* Append partition tablespace.
* Skip it, if partition tablespace is the same as partitioned table.
*/
int fno = SPI_fnumber(spi_tupdesc, "reltblspc");
const char *spcname = SPI_getvalue(spi_tuple, spi_tupdesc, fno);
AppendTablespaceInfo(spcname, buf, tableinfo);
if (subpartinfo->issubpartition) {
subpartinfo->subparentid =
atooid(SPI_getvalue(spi_tuple, spi_tupdesc, SPI_fnumber(spi_tupdesc, "partoid")));
AppendSubPartitionDetail(buf, tableinfo, subpartinfo);
}
}
DestroyStringInfo(query);
appendStringInfo(buf, "\n)");
}
static void AppendTablespaceInfo(const char *spcname, StringInfo buf, tableInfo tableinfo)
{
if (spcname != NULL) {
appendStringInfo(buf, " TABLESPACE %s", quote_identifier(spcname));
}
}
* @Description: get collation's namespace oid by collation oid.
* @in colloid - collation oid.
* @return - namespace oid.
*/
static Oid get_collation_namespace(Oid colloid)
{
HeapTuple tp = NULL;
Oid result = InvalidOid;
tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(colloid));
if (HeapTupleIsValid(tp)) {
Form_pg_collation colltup = (Form_pg_collation)GETSTRUCT(tp);
Oid space_oid = colltup->collnamespace;
result = space_oid;
}
ReleaseSysCache(tp);
return result;
}
static void get_compression_mode(Form_pg_attribute att_tup, StringInfo buf)
{
switch (att_tup->attcmprmode) {
case ATT_CMPR_DELTA:
appendStringInfoString(buf, " DELTA");
break;
case ATT_CMPR_DICTIONARY:
appendStringInfoString(buf, " DICTIONARY");
break;
case ATT_CMPR_PREFIX:
appendStringInfoString(buf, " PREFIX");
break;
case ATT_CMPR_NUMSTR:
appendStringInfoString(buf, " NUMSTR");
break;
default:
break;
}
}
* @Description: get table's attribute infomartion.
* @in tableoid - table oid.
* @in buf - the string to append attribute info.
* @in formatter -the formatter for foreign table.
* @in ft_frmt_clmn -formatter columns.
* @in cnt_ft_frmt_clmns - number of formatter columns.
* @return - number of attribute.
*/
static int get_table_attribute(
Oid tableoid, StringInfo buf, char* formatter, char** ft_frmt_clmn, int cnt_ft_frmt_clmns, tableInfo* tableinfo)
{
int natts = get_relnatts(tableoid);
int i;
int actual_atts = 0;
for (i = 0; i < natts; i++) {
HeapTuple tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tableoid), Int16GetDatum(i + 1));
if (HeapTupleIsValid(tp)) {
Form_pg_attribute att_tup = (Form_pg_attribute)GETSTRUCT(tp);
if (att_tup->attkvtype == ATT_KV_HIDE) {
ReleaseSysCache(tp);
continue;
}
char* result = NULL;
Datum txt;
if (att_tup->attisdropped) {
ReleaseSysCache(tp);
continue;
}
if (type_is_set(att_tup->atttypid)) {
txt = GetSetDefineStr(att_tup->atttypid);
result = TextDatumGetCString(txt);
} else {
txt = DirectFunctionCall2(
format_type, ObjectIdGetDatum(att_tup->atttypid), ObjectIdGetDatum(att_tup->atttypmod));
result = TextDatumGetCString(txt);
result = format_type_with_typemod(att_tup->atttypid, att_tup->atttypmod);
}
actual_atts == 0 ? appendStringInfo(buf, " (") : appendStringInfo(buf, ",");
appendStringInfo(buf, "\n ");
actual_atts++;
bool iscomputedcol = IsComputedColumn(tableoid, i + 1);
if (iscomputedcol) {
appendStringInfo(buf, "%s", quote_identifier(NameStr(att_tup->attname)));
} else {
appendStringInfo(buf, "%s %s", quote_identifier(NameStr(att_tup->attname)), result);
}
if (att_tup->attkvtype == ATT_KV_TAG)
appendStringInfo(buf, " TSTag");
else if (att_tup->attkvtype == ATT_KV_FIELD)
appendStringInfo(buf, " TSField");
else if (att_tup->attkvtype == ATT_KV_TIMETAG)
appendStringInfo(buf, " TSTime");
get_compression_mode(att_tup, buf);
if (OidIsValid(att_tup->attcollation) && att_tup->attcollation != get_typcollation(att_tup->atttypid)) {
char* collname = get_collation_name(att_tup->attcollation);
int charset = get_charset_by_collation(att_tup->attcollation);
if (DB_IS_CMPT_BD && charset != PG_INVALID_ENCODING) {
appendStringInfo(
buf, " CHARACTER SET %s", quote_identifier(pg_encoding_to_char(charset)));
appendStringInfo(
buf, " COLLATE %s", quote_identifier(collname));
} else {
Oid namespace_oid = get_collation_namespace(att_tup->attcollation);
char* namespace_name = get_namespace_name(namespace_oid);
appendStringInfo(
buf, " COLLATE %s.%s", quote_identifier(namespace_name), quote_identifier(collname));
pfree_ext(namespace_name);
}
pfree_ext(collname);
}
if (formatter != NULL) {
int iter;
char* relname = NameStr(att_tup->attname);
for (iter = 0; iter < cnt_ft_frmt_clmns; iter++) {
if ((0 == strncmp(relname, ft_frmt_clmn[iter], strlen(relname))) &&
('(' == ft_frmt_clmn[iter][strlen(relname)])) {
appendStringInfo(buf, " position%s", &ft_frmt_clmn[iter][strlen(relname)]);
}
}
}
if (att_tup->atthasdef) {
Form_pg_attrdef attrdef;
Relation attrdefDesc;
ScanKeyData skey[1];
SysScanDesc adscan;
HeapTuple tup = NULL;
bool isnull = false;
char generatedCol = '\0';
bool isDefault = false;
bool isOnUpdate = false;
attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
ScanKeyInit(
&skey[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
adscan = systable_beginscan(attrdefDesc, AttrDefaultIndexId, true, NULL, 1, skey);
while (HeapTupleIsValid(tup = systable_getnext(adscan))) {
attrdef = (Form_pg_attrdef)GETSTRUCT(tup);
Datum val = fastgetattr(tup, Anum_pg_attrdef_adbin, attrdefDesc->rd_att, &isnull);
Datum txt = DirectFunctionCall2(pg_get_expr, val, ObjectIdGetDatum(tableoid));
if (txt && pg_strcasecmp(TextDatumGetCString(txt), "") == 0) {
isDefault = false;
} else {
isDefault = true;
}
Datum onUpdateExpr = fastgetattr(tup, Anum_pg_attrdef_adsrc_on_update, attrdefDesc->rd_att, &isnull);
if (onUpdateExpr && pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "") != 0) {
isOnUpdate = true;
} else {
isOnUpdate = false;
}
if (attrdef->adnum == att_tup->attnum) {
Datum adgencol = fastgetattr(tup, Anum_pg_attrdef_adgencol, attrdefDesc->rd_att, &isnull);
if (!isnull) {
generatedCol = DatumGetChar(adgencol);
}
const char* adsrc = TextDatumGetCString(txt);
if (generatedCol == ATTRIBUTE_GENERATED_STORED) {
appendStringInfo(buf, " GENERATED ALWAYS AS (%s) STORED", adsrc);
} else if (generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
appendStringInfo(buf, " AS (%s) PERSISTED", adsrc);
} else if (strcmp(adsrc, "AUTO_INCREMENT") == 0) {
Node *adexpr = (Node*)stringToNode_skip_extern_fields(TextDatumGetCString(val));
find_nextval_seqoid_walker(adexpr, &tableinfo->autoinc_seqoid);
tableinfo->autoinc_attnum = attrdef->adnum;
appendStringInfo(buf, " %s", adsrc);
} else if (pg_strcasecmp(adsrc, "") != 0) {
appendStringInfo(buf, " DEFAULT %s", adsrc);
}
if (isOnUpdate) {
if (pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "pg_systimestamp()") == 0) {
appendStringInfo(buf, " ON UPDATE CURRENT_TIMESTAMP");
} else if (pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "('now'::text)::time with time zone") == 0) {
appendStringInfo(buf, " ON UPDATE CURRENT_TIME");
} else if (pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "text_date('now'::text)") == 0) {
appendStringInfo(buf, " ON UPDATE CURRENT_DATE");
} else if (pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "('now'::text)::time without time zone") == 0) {
appendStringInfo(buf, " ON UPDATE LOCALTIME");
} else if (pg_strcasecmp(TextDatumGetCString(onUpdateExpr), "('now'::text)::timestamp without time zone") == 0) {
appendStringInfo(buf, " ON UPDATE LOCALTIMESTAMP");
} else {
char* size_start = NULL;
char* size_end = NULL;
int number_size = 0;
size_start = strstr(TextDatumGetCString(onUpdateExpr), "timestamp(");
if (size_start != NULL) {
size_start = size_start + 10;
size_end = strstr(size_start, ")");
number_size = size_end - size_start;
char num[number_size + 1] = {0};
errno_t rc = strncpy_s(num, sizeof(num), size_start, number_size);
securec_check_c(rc, "\0", "\0");
appendStringInfo(buf, " ON UPDATE CURRENT_TIMESTAMP(%s)", num);
} else {
size_start = strstr(TextDatumGetCString(onUpdateExpr), "time(");
if (size_start != NULL) {
size_start = size_start + 5;
size_end = strstr(size_start, ")");
number_size = size_end - size_start;
char num[number_size + 1] = {0};
errno_t rc = strncpy_s(num, sizeof(num), size_start, number_size);
securec_check_c(rc, "\0", "\0");
appendStringInfo(buf, " ON UPDATE CURRENT_TIME(%s)", num);
}
}
}
}
break;
}
}
systable_endscan(adscan);
heap_close(attrdefDesc, AccessShareLock);
}
if (att_tup->attnotnull)
appendStringInfo(buf, " NOT NULL");
ReleaseSysCache(tp);
}
}
return actual_atts;
}
bool IsHideTagDistribute(Oid relOid)
{
Relation pcrel = NULL;
ScanKeyData skey;
SysScanDesc pcscan = NULL;
HeapTuple htup = NULL;
Form_pgxc_class pgxc_class;
bool hide = false;
ScanKeyInit(&skey, Anum_pgxc_class_pcrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid));
pcrel = heap_open(PgxcClassRelationId, AccessShareLock);
pcscan = systable_beginscan(pcrel, PgxcClassPgxcRelIdIndexId, true, NULL, 1, &skey);
htup = systable_getnext(pcscan);
if (!HeapTupleIsValid(htup)) {
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not open relation with OID %u", relOid),
errdetail("Cannot open pgxcclass."),
errcause("Cannot open pgxcclass."),
erraction("Retry."),
errmodule(MOD_OPT)));
}
pgxc_class = (Form_pgxc_class)GETSTRUCT(htup);
if (pgxc_class->pcattnum.dim1 == 1 && get_kvtype(relOid, pgxc_class->pcattnum.values[0]) == ATT_KV_HIDE) {
hide = true;
}
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
return hide;
}
* @Description: get table's distribute key.
* @in relOid - table oid.
* @return - distribute key.
*/
char* printDistributeKey(Oid relOid)
{
Relation pcrel = NULL;
ScanKeyData skey;
SysScanDesc pcscan = NULL;
HeapTuple htup = NULL;
Form_pgxc_class pgxc_class;
StringInfoData attname = {NULL, 0, 0, 0};
initStringInfo(&attname);
ScanKeyInit(&skey, Anum_pgxc_class_pcrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid));
pcrel = heap_open(PgxcClassRelationId, AccessShareLock);
pcscan = systable_beginscan(pcrel, PgxcClassPgxcRelIdIndexId, true, NULL, 1, &skey);
htup = systable_getnext(pcscan);
if (!HeapTupleIsValid(htup)) {
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not open relation with OID %u", relOid)));
}
pgxc_class = (Form_pgxc_class)GETSTRUCT(htup);
if (pgxc_class->pcattnum.values[0] == InvalidAttrNumber) {
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
return NULL;
}
for (int i = 0; i < pgxc_class->pcattnum.dim1; i++) {
if (i == 0) {
appendStringInfoString(&attname, quote_identifier(get_attname(relOid, pgxc_class->pcattnum.values[i])));
} else {
appendStringInfoString(&attname, ", ");
appendStringInfoString(&attname, quote_identifier(get_attname(relOid, pgxc_class->pcattnum.values[i])));
}
}
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
return attname.data;
}
* @Description: get changing table's info (alter table and comments).
* @in tableoid - table oid.
* @in buf - string to append changing info.
* @in relname - table name.
* @in relkind - relkind of the table.
* @return - void
*/
static void get_changing_table_info(Oid tableoid, StringInfo buf, const char* relname, char relkind)
{
char* comment = NULL;
Relation pg_constraint = NULL;
HeapTuple tuple = NULL;
SysScanDesc scan;
ScanKeyData skey[1];
comment = GetComment(tableoid, RelationRelationId, 0);
if (comment != NULL) {
appendStringInfo(buf, "\nCOMMENT ON TABLE %s IS '%s';", relname, comment);
pfree_ext(comment);
}
int natts = get_relnatts(tableoid);
for (int i = 0; i < natts; i++) {
HeapTuple tp;
tp = SearchSysCache2(ATTNUM, ObjectIdGetDatum(tableoid), Int16GetDatum(i + 1));
if (HeapTupleIsValid(tp)) {
Form_pg_attribute att_tup = (Form_pg_attribute)GETSTRUCT(tp);
const char* storage = NULL;
if (att_tup->attisdropped) {
ReleaseSysCache(tp);
continue;
}
if (att_tup->attstattarget >= 0) {
if (relkind == RELKIND_FOREIGN_TABLE) {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
} else if (relkind == RELKIND_STREAM) {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
} else {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
}
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "SET STATISTICS %d;", att_tup->attstattarget);
}
if (att_tup->attstattarget < -1) {
int percent_value;
percent_value = (att_tup->attstattarget + 1) * (-1);
if (relkind == RELKIND_FOREIGN_TABLE) {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
} else if (relkind == RELKIND_STREAM) {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
} else {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
}
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "SET STATISTICS PERCENT %d;", percent_value);
}
if (get_typstorage(att_tup->atttypid) != att_tup->attstorage) {
switch (att_tup->attstorage) {
case 'p':
storage = "PLAIN";
break;
case 'e':
storage = "EXTERNAL";
break;
case 'm':
storage = "MAIN";
break;
case 'x':
storage = "EXTENDED";
break;
default:
storage = NULL;
break;
}
if (storage != NULL) {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "SET STORAGE %s;", storage);
}
}
* Dump per-column attributes.
*/
Datum datum;
bool isNull = false;
datum = SysCacheGetAttr(ATTNUM, tp, Anum_pg_attribute_attoptions, &isNull);
if (!isNull) {
Datum sep = CStringGetTextDatum(", ");
Datum txt = OidFunctionCall2(F_ARRAY_TO_TEXT, datum, sep);
char* attoptions = TextDatumGetCString(txt);
if (attoptions != NULL && attoptions[0] != '\0') {
if (relkind == RELKIND_FOREIGN_TABLE) {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
} else if (relkind == RELKIND_STREAM) {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
} else {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
}
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "SET (%s);", attoptions);
pfree_ext(attoptions);
}
}
* Dump per-column fdw options.
*/
datum = SysCacheGetAttr(ATTNUM, tp, Anum_pg_attribute_attfdwoptions, &isNull);
if (!isNull && relkind == RELKIND_FOREIGN_TABLE) {
Datum sep = CStringGetTextDatum(", ");
Datum txt = OidFunctionCall2(F_ARRAY_TO_TEXT, datum, sep);
char* attfdwoptions = TextDatumGetCString(txt);
if (attfdwoptions != NULL && attfdwoptions[0] != '\0') {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "OPTIONS (\n %s\n);", attfdwoptions);
}
}
if (!isNull && relkind == RELKIND_STREAM) {
Datum sep = CStringGetTextDatum(", ");
Datum txt = OidFunctionCall2(F_ARRAY_TO_TEXT, datum, sep);
char* attfdwoptions = TextDatumGetCString(txt);
if (attfdwoptions != NULL && attfdwoptions[0] != '\0') {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
appendStringInfo(buf, "ALTER COLUMN %s ", quote_identifier(NameStr(att_tup->attname)));
appendStringInfo(buf, "OPTIONS (\n %s\n);", attfdwoptions);
}
}
comment = GetComment(tableoid, RelationRelationId, i + 1);
if (comment != NULL) {
appendStringInfo(buf,
"\nCOMMENT ON COLUMN %s.%s IS '%s';",
relname,
quote_identifier(NameStr(att_tup->attname)),
comment);
pfree_ext(comment);
}
}
ReleaseSysCache(tp);
}
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, SnapshotNow, 1, skey);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);
if (con->contype == 'c') {
Oid conOid = HeapTupleGetOid(tuple);
char* comment = GetComment(conOid, ConstraintRelationId, 0);
if (comment != NULL) {
appendStringInfo(buf,
"\nCOMMENT ON CONSTRAINT %s ON %s IS '%s';",
quote_identifier(NameStr(con->conname)),
relname,
comment);
pfree_ext(comment);
}
}
}
systable_endscan(scan);
heap_close(pg_constraint, AccessShareLock);
}
* @Description: get table's constraint info.
* @in conForm - constraint info.
* @in tuple - tuple for constraint.
* @in buf - string to append comment info.
* @in constraintId - constraint oid.
* @in relname - table name.
* @return - void
*/
static void get_table_constraint_info(
Form_pg_constraint conForm, HeapTuple tuple, StringInfo buf, Oid constraintId, const char* relname)
{
bool isnull = false;
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
appendStringInfo(buf, "ADD CONSTRAINT %s ", quote_identifier(NameStr(conForm->conname)));
if (conForm->contype == CONSTRAINT_PRIMARY) {
appendStringInfo(buf, "PRIMARY KEY ");
} else {
appendStringInfo(buf, "UNIQUE ");
}
if (DB_IS_CMPT(B_FORMAT)) {
Oid indexrelid_con;
indexrelid_con = conForm->conindid;
HeapTuple ht_idxrel;
HeapTuple ht_am;
Form_pg_class idxrelrec;
Form_pg_am amrec;
ht_idxrel = SearchSysCache1((int)RELOID, ObjectIdGetDatum(indexrelid_con));
if (!HeapTupleIsValid(ht_idxrel))
ereport(
ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", indexrelid_con)));
idxrelrec = (Form_pg_class)GETSTRUCT(ht_idxrel);
ht_am = SearchSysCache1((int)AMOID, ObjectIdGetDatum(idxrelrec->relam));
if (!HeapTupleIsValid(ht_am))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for access method %u", idxrelrec->relam)));
amrec = (Form_pg_am)GETSTRUCT(ht_am);
appendStringInfo(buf, "USING %s ", quote_identifier(NameStr(amrec->amname)));
char* conInfo = pg_get_constraintdef_worker(constraintId, false, 0);
appendStringInfo(buf, "%s", conForm->contype == 'p' ?
conInfo + PRIMARY_KEY_OFFSET : conInfo + UNIQUE_OFFSET);
ReleaseSysCache(ht_idxrel);
ReleaseSysCache(ht_am);
} else {
appendStringInfo(buf, "(");
Datum val = SysCacheGetAttr(CONSTROID, tuple, Anum_pg_constraint_conkey, &isnull);
if (isnull) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("null conkey for constraint %u", constraintId)));
}
decompile_column_index_array(val, conForm->conrelid, buf);
appendStringInfo(buf, ")");
}
Oid indexId = get_constraint_index(constraintId);
if (OidIsValid(indexId)) {
char* options = flatten_reloptions(indexId);
Oid tblspc;
if (options != NULL) {
appendStringInfo(buf, " WITH (%s)", options);
pfree_ext(options);
}
tblspc = get_rel_tablespace(indexId);
if (OidIsValid(tblspc)) {
char* spcname = get_tablespace_name(tblspc);
if (spcname != NULL) {
appendStringInfo(buf, " USING INDEX TABLESPACE %s", quote_identifier(spcname));
} else {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace with OID %u does not exist", tblspc)));
}
}
}
if (conForm->condeferrable)
appendStringInfo(buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(buf, " INITIALLY DEFERRED");
if (!conForm->convalidated)
appendStringInfoString(buf, " NOT VALID");
}
* @Description: get foreign table's constraint info.
* @in buf - string to append comment info.
* @in tableoid - table oid.
* @in relname - table name.
* @return - void
*/
static void get_foreign_constraint_info(StringInfo buf, Oid tableoid, const char* relname)
{
Relation pg_constraint = NULL;
HeapTuple tuple = NULL;
SysScanDesc scan = NULL;
ScanKeyData skey[1];
bool isnull = false;
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, NULL, 1, skey);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);
Oid conOid = HeapTupleGetOid(tuple);
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
appendStringInfo(buf, "ADD CONSTRAINT %s", quote_identifier(NameStr(con->conname)));
appendStringInfo(buf, " %s (", (con->contype == 'p') ? "PRIMARY KEY" : "UNIQUE");
Datum val = SysCacheGetAttr(CONSTROID, tuple, Anum_pg_constraint_conkey, &isnull);
if (isnull) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("null conkey for constraint %u", conOid)));
}
decompile_column_index_array(val, con->conrelid, buf);
appendStringInfo(buf, ")");
if (con->consoft) {
appendStringInfo(buf, " NOT ENFORCED");
if (con->conopt) {
appendStringInfo(buf, " ENABLE QUERY OPTIMIZATION");
} else {
appendStringInfo(buf, " DISABLE QUERY OPTIMIZATION");
}
}
appendStringInfo(buf, ";");
}
systable_endscan(scan);
heap_close(pg_constraint, AccessShareLock);
}
* @Description: get table's index info.
* @in tableoid - table oid.
* @in buf - string to append comment info.
* @in relname - table name.
* @return - void
*/
static void get_index_list_info(Oid tableoid, StringInfo buf, const char* relname, tableInfo* tableinfo)
{
Relation indrel = NULL;
SysScanDesc indscan = NULL;
ScanKeyData skey;
HeapTuple htup = NULL;
Oid constriantid = InvalidOid;
ScanKeyInit(&skey, Anum_pg_index_indrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
indrel = heap_open(IndexRelationId, AccessShareLock);
indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true, NULL, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(indscan))) {
Form_pg_index index = (Form_pg_index)GETSTRUCT(htup);
* Ignore any indexes that are currently being dropped. This will
* prevent them from being searched, inserted into, or considered in
* HOT-safety decisions. It's unsafe to touch such an index at all
* since its catalog entries could disappear at any instant.
*/
if (!IndexIsLive(index))
continue;
constriantid = get_index_constraint(index->indexrelid);
if (OidIsValid(constriantid)) {
if (tableinfo->autoinc_consoid != constriantid) {
HeapTuple tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constriantid));
if (!HeapTupleIsValid(tup)) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("cache lookup failed for constraint %u", constriantid)));
}
Form_pg_constraint conForm = (Form_pg_constraint)GETSTRUCT(tup);
if (conForm->contype == CONSTRAINT_UNIQUE || conForm->contype == CONSTRAINT_PRIMARY) {
get_table_constraint_info(conForm, tup, buf, constriantid, relname);
appendStringInfo(buf, ";");
} else {
appendStringInfo(buf, "\n%s;", pg_get_indexdef_worker(index->indexrelid, 0, NULL, false, true, 0));
}
ReleaseSysCache(tup);
}
} else {
appendStringInfo(buf, "\n%s;", pg_get_indexdef_worker(index->indexrelid, 0, NULL, false, true, 0));
if (index->indisclustered) {
appendStringInfo(buf, "\nALTER TABLE %s CLUSTER", relname);
appendStringInfo(buf, " ON %s;", quote_identifier(get_rel_name(index->indexrelid)));
}
}
char* comment = GetComment(index->indexrelid, RelationRelationId, 0);
if (comment != NULL) {
appendStringInfo(
buf, "\nCOMMENT ON INDEX %s IS '%s';", quote_identifier(get_rel_name(index->indexrelid)), comment);
pfree_ext(comment);
}
}
systable_endscan(indscan);
heap_close(indrel, AccessShareLock);
}
static void append_table_autoinc_clause(StringInfo buf, Oid seqoid)
{
char strbuf[MAXINT128LEN + 1];
int128 val = autoinc_get_nextval(seqoid);
pg_i128toa(val, strbuf, MAXINT128LEN + 1);
appendStringInfo(buf, " AUTO_INCREMENT = %s", strbuf);
}
static void append_table_charset_collate_clause(StringInfo buf, Oid collate)
{
const char* coll_name = get_collation_name(collate);
const char* charset_name = pg_encoding_to_char(get_charset_by_collation(collate));
appendStringInfo(buf, "\nCHARACTER SET = \"%s\" COLLATE = \"%s\"", charset_name, coll_name);
}
* @Description: append table's info.
* @in tableinfo - parent table info.
* @in srvname - server name.
* @in buf - string to append comment info.
* @in tableoid - table oid.
* @in isHDFSTbl - is a hdfs table
* @in query - string for spi execute
* @in ftoptions - options for foreign table
* @in ft_write_only - write only
* @return - is hdfs foreign table
*/
static bool append_table_info(tableInfo tableinfo, const char* srvname, StringInfo buf, Oid tableoid, bool isHDFSTbl,
StringInfo query, char* ftoptions, bool ft_write_only)
{
bool IsHDFSFTbl = false;
int spirc;
int proc;
if (OidIsValid(tableinfo.autoinc_seqoid)) {
append_table_autoinc_clause(buf, tableinfo.autoinc_seqoid);
}
if (OidIsValid(tableinfo.collate)) {
append_table_charset_collate_clause(buf, tableinfo.collate);
}
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE || tableinfo.relkind == RELKIND_STREAM)
appendStringInfo(buf, "\nSERVER %s", quote_identifier(srvname));
if (tableinfo.reloptions && strlen(tableinfo.reloptions) > 0) {
appendStringInfo(buf, "\nWITH (%s)", tableinfo.reloptions);
}
if (REL_CMPRS_FIELDS_EXTRACT == tableinfo.relcmpr) {
appendStringInfo(buf, "\nCOMPRESS");
}
if (tableinfo.relkind != RELKIND_FOREIGN_TABLE || tableinfo.relkind != RELKIND_STREAM) {
if (OidIsValid(tableinfo.tablespace)) {
char* spcname = get_tablespace_name(tableinfo.tablespace);
if (spcname != NULL) {
appendStringInfo(buf, "\nTABLESPACE %s", quote_identifier(spcname));
} else {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace with OID %u does not exist", tableinfo.tablespace)));
}
}
char locator_type = GetLocatorType(tableoid);
if (IsLocatorColumnDistributed(locator_type) || IsLocatorReplicated(locator_type)) {
appendStringInfo(buf, "\nDISTRIBUTE BY");
Relation rel = heap_open(tableoid, NoLock);
if (RelationIsTsStore(rel) && IsHideTagDistribute(tableoid)) {
appendStringInfo(buf, " hidetag");
} else if (locator_type == LOCATOR_TYPE_HASH) {
char* distribute_key = printDistributeKey(tableoid);
appendStringInfo(buf, " HASH(%s)", distribute_key);
} else if (locator_type == LOCATOR_TYPE_REPLICATED) {
appendStringInfo(buf, " REPLICATION");
} else if (locator_type == LOCATOR_TYPE_RROBIN) {
appendStringInfo(buf, " ROUNDROBIN");
} else if (locator_type == LOCATOR_TYPE_MODULO) {
char* distribute_key = printDistributeKey(tableoid);
appendStringInfo(buf, " MODULO (%s)", distribute_key);
} else if (locator_type == LOCATOR_TYPE_RANGE) {
char* distribute_key = printDistributeKey(tableoid);
appendStringInfo(buf, " RANGE(%s)", distribute_key);
GetRangeDistributionDef(query, buf, tableoid);
} else {
char* distribute_key = printDistributeKey(tableoid);
appendStringInfo(buf, " LIST(%s)", distribute_key);
GetListDistributionDef(query, buf, tableoid);
}
heap_close(rel, NoLock);
}
if (!isHDFSTbl && IS_PGXC_COORDINATOR) {
Oid group_oid = get_pgxc_class_groupoid(tableoid);
char* group_name = get_pgxc_groupname(group_oid, NULL);
if (group_name != NULL) {
appendStringInfo(buf, "\nTO GROUP %s", quote_identifier(group_name));
} else {
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("computing nodegroup is not a valid group.")));
}
pfree_ext(group_name);
}
* Show information about partition table.
* 1. Get the partition key postition and partition strategy from pg_partition.
*/
get_table_partitiondef(query, buf, tableoid, tableinfo);
} else {
* Judge whether the relation is a HDFS foreign table.
*/
char* fdwnamestr = NULL;
ForeignServer* Server = GetForeignServerByName(srvname, false);
ForeignDataWrapper* wrapper = GetForeignDataWrapper(Server->fdwid);
fdwnamestr = wrapper->fdwname;
if (strncasecmp(fdwnamestr, HDFS_FDW, NAMEDATALEN) == 0 || strncasecmp(fdwnamestr, DFS_FDW, NAMEDATALEN) == 0 ||
strncasecmp(fdwnamestr, "log_fdw", NAMEDATALEN) == 0) {
IsHDFSFTbl = true;
}
}
if (ftoptions != NULL && ftoptions[0]) {
if ((NULL != strstr(ftoptions, "error_table")) || (NULL != strstr(ftoptions, "formatter")) ||
(NULL != strstr(ftoptions, "log_remote")) || (NULL != strstr(ftoptions, "reject_limit"))) {
int i_error_table;
int i_log_remote;
int i_reject_limit;
int i_ftoptions;
char* error_table = NULL;
char* log_remote = NULL;
char* reject_limit = NULL;
char* ftoptionsUpdated = NULL;
resetStringInfo(query);
appendStringInfo(query,
"WITH ft_options AS "
"(SELECT (pg_catalog.pg_options_to_table(ft.ftoptions)).option_name, "
"(pg_catalog.pg_options_to_table(ft.ftoptions)).option_value "
"FROM pg_catalog.pg_foreign_table ft "
"WHERE ft.ftrelid = %u) "
"SELECT "
"( SELECT option_value FROM ft_options WHERE option_name = 'error_table' ) AS error_table,"
"( SELECT option_value FROM ft_options WHERE option_name = 'log_remote' ) AS log_remote,"
"( SELECT option_value FROM ft_options WHERE option_name = 'reject_limit' ) AS reject_limit,"
"pg_catalog.array_to_string(ARRAY( "
"SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value) "
"FROM ft_options WHERE option_name <> 'error_table' AND option_name <> 'log_remote' "
"AND option_name <> 'reject_limit' AND option_name <> 'formatter' ORDER BY option_name "
"), E',\n ') AS ftoptions;",
tableoid);
spirc = SPI_execute(query->data, true, INT_MAX);
proc = SPI_processed;
i_ftoptions = SPI_fnumber(SPI_tuptable->tupdesc, "ftoptions");
i_error_table = SPI_fnumber(SPI_tuptable->tupdesc, "error_table");
i_log_remote = SPI_fnumber(SPI_tuptable->tupdesc, "log_remote");
i_reject_limit = SPI_fnumber(SPI_tuptable->tupdesc, "reject_limit");
ftoptionsUpdated = pstrdup(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_ftoptions));
char* tmp_value = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_error_table);
if (tmp_value != NULL) {
error_table = pstrdup(tmp_value);
}
tmp_value = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_log_remote);
if (tmp_value != NULL) {
log_remote = pstrdup(tmp_value);
}
tmp_value = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_reject_limit);
if (tmp_value != NULL) {
reject_limit = pstrdup(tmp_value);
}
appendStringInfo(buf, "\nOPTIONS (\n %s\n)", ftoptionsUpdated);
if (ft_write_only) {
appendStringInfo(buf, " WRITE ONLY");
}
if (NULL != error_table) {
appendStringInfo(buf, " WITH %s", quote_identifier(error_table));
pfree_ext(error_table);
}
if (NULL != log_remote) {
appendStringInfo(buf, " REMOTE LOG '%s'", log_remote);
pfree_ext(log_remote);
}
if (NULL != reject_limit) {
appendStringInfo(buf, " PER NODE REJECT LIMIT '%s'", reject_limit);
pfree_ext(reject_limit);
}
if (ftoptionsUpdated != NULL)
pfree_ext(ftoptionsUpdated);
} else {
appendStringInfo(buf, "\nOPTIONS (\n %s\n)", ftoptions);
if (ft_write_only) {
appendStringInfo(buf, " WRITE ONLY");
}
}
}
return IsHDFSFTbl;
}
* @Description: append table's alter table info.
* @in tableinfo - parent table info.
* @in has_not_valid_check - check is not valid
* @in buf - string to append comment info.
* @in tableoid - table oid.
* @in relname -table name
* @return - void
*/
static void get_table_alter_info(
tableInfo tableinfo, bool has_not_valid_check, StringInfo buf, Oid tableoid, const char* relname)
{
Relation pg_constraint = NULL;
HeapTuple tuple = NULL;
SysScanDesc scan = NULL;
ScanKeyData skey[1];
if (tableinfo.hasindex) {
get_index_list_info(tableoid, buf, relname, &tableinfo);
}
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE || tableinfo.relkind == RELKIND_STREAM) {
get_foreign_constraint_info(buf, tableoid, relname);
}
if (tableinfo.hasPartialClusterKey || has_not_valid_check) {
* Fetch the constraint tuple from pg_constraint. There may be more than
* one match, because constraints are not required to have unique names;
* if so, error out.
*/
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, NULL, 1, skey);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);
if (tableinfo.hasPartialClusterKey && con->contype == 's') {
Oid conOid = HeapTupleGetOid(tuple);
char* cluster_constraintdef =
pg_get_constraintdef_worker(conOid, false, PRETTYFLAG_PAREN | PRETTYFLAG_INDENT);
if (cluster_constraintdef != NULL) {
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE) {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
} else if (tableinfo.relkind == RELKIND_STREAM) {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
} else {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
}
appendStringInfo(buf, "ADD %s;", cluster_constraintdef);
}
} else if (has_not_valid_check && con->contype == 'c' && !con->convalidated) {
Oid conOid = HeapTupleGetOid(tuple);
char* cluster_constraintdef =
pg_get_constraintdef_worker(conOid, false, PRETTYFLAG_PAREN | PRETTYFLAG_INDENT);
if (cluster_constraintdef != NULL) {
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE) {
appendStringInfo(buf, "\nALTER FOREIGN TABLE %s ", relname);
} else if (tableinfo.relkind == RELKIND_STREAM) {
appendStringInfo(buf, "\nALTER STREAM %s ", relname);
} else {
appendStringInfo(buf, "\nALTER TABLE %s ", relname);
}
appendStringInfo(buf, "ADD CONSTRAINT %s ", quote_identifier(NameStr(con->conname)));
appendStringInfo(buf, "%s;", cluster_constraintdef);
}
}
}
systable_endscan(scan);
heap_close(pg_constraint, AccessShareLock);
}
}
static Oid SearchSysTable(const char* query)
{
* Connect to SPI manager
*/
SPI_STACK_LOG("connect", NULL, NULL);
if (SPI_connect() != SPI_OK_CONNECT) {
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
}
Oid oid = InvalidOid;
int spirc = SPI_execute(query, true, INT_MAX);
int proc = SPI_processed;
if ((spirc == SPI_OK_SELECT) && (proc > 0)) {
int fnumber = SPI_fnumber(SPI_tuptable->tupdesc, "oid");
bool isnull = false;
oid = (Oid)SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, fnumber, &isnull);
if(isnull) {
oid = InvalidOid;
}
}
* Disconnect from SPI manager
*/
SPI_STACK_LOG("finish", NULL, NULL);
if (SPI_finish() != SPI_OK_FINISH) {
ereport(ERROR, (errcode(ERRCODE_SPI_FINISH_FAILURE), errmsg("SPI_finish failed")));
}
return oid;
}
static inline bool IsTableVisible(Oid tableoid)
{
StringInfoData query;
initStringInfo(&query);
appendStringInfo(&query, "select oid from %s where oid = %u", "pg_catalog.pg_class", tableoid);
Oid oid = SearchSysTable(query.data);
pfree_ext(query.data);
return OidIsValid(oid);
}
* @Description: in B-format, remove the collate attribute from reloptions and
* add collate information to tableinfo.
* @in reloptions - old reloptions.
* @in tableinfo - table information.
* @return - new reloptions.
*/
static Datum transform_reloptions_collate(Datum reloptions, tableInfo* tableinfo)
{
List* options = NULL;
ListCell* lcell = NULL;
DefElem* opt = NULL;
tableinfo->collate = InvalidOid;
if (!DB_IS_CMPT(B_FORMAT)) {
return reloptions;
}
options = untransformRelOptions(reloptions);
foreach(lcell, options) {
opt = (DefElem*)lfirst(lcell);
if (0 == strncmp(opt->defname, "collate", strlen("collate"))) {
tableinfo->collate = pg_strtoint32(strVal(opt->arg));
options = list_delete_ptr(options, opt);
break;
}
}
reloptions = transformRelOptions((Datum)0, options, NULL, NULL, false, false);
list_free_deep(options);
return reloptions;
}
* @Description: get table's defination by table oid.
* @in tableoid - table oid.
* @return - table's defination.
*/
static char* pg_get_tabledef_worker(Oid tableoid)
{
StringInfoData buf;
StringInfoData query;
const char* reltypename = NULL;
char* relOfTypeName = NULL;
int actual_atts = 0;
bool isnull = false;
char* srvname = NULL;
char* ftoptions = NULL;
bool ft_write_only = false;
char* formatter = NULL;
int cnt_ft_frmt_clmns = 0;
char** ft_frmt_clmn = NULL;
bool IsHDFSFTbl = false;
bool isHDFSTbl = false;
tableInfo tableinfo;
Form_pg_class classForm = NULL;
int spirc;
int proc;
const char* relname = NULL;
Relation pg_constraint = NULL;
HeapTuple tuple = NULL;
SysScanDesc scan = NULL;
ScanKeyData skey[1];
bool has_not_valid_check = false;
tableinfo.reloptions = NULL;
if (IsTempTable(tableoid)) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Can not get temporary tables defination.")));
}
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(tableoid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for table %u.", tableoid)));
}
if (!IsTableVisible(tableoid)) {
ReleaseSysCache(tuple);
return NULL;
}
* Do this first so that string is alloc'd in outer context not SPI's.
*/
initStringInfo(&buf);
initStringInfo(&query);
classForm = (Form_pg_class)GETSTRUCT(tuple);
tableinfo.relkind = classForm->relkind;
if (tableinfo.relkind != RELKIND_FOREIGN_TABLE && tableinfo.relkind != RELKIND_STREAM
&& tableinfo.relkind != RELKIND_RELATION) {
ReleaseSysCache(tuple);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Not a ordinary table or foreign table.")));
}
tableinfo.collate = 0;
Datum reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
if (isnull == false) {
reloptions = transform_reloptions_collate(reloptions, &tableinfo);
Datum sep = CStringGetTextDatum(", ");
Datum txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
tableinfo.reloptions = TextDatumGetCString(txt);
* skipping error tables
*/
if (NULL != strstr(tableinfo.reloptions, "internal_mask")) {
ReleaseSysCache(tuple);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Not a ordinary table or foreign table.")));
}
}
tableinfo.relpersistence = classForm->relpersistence;
tableinfo.tablespace = classForm->reltablespace;
tableinfo.hasPartialClusterKey = classForm->relhasclusterkey;
tableinfo.hasindex = classForm->relhasindex;
tableinfo.relcmpr = classForm->relcmprs;
tableinfo.relrowmovement = classForm->relrowmovement;
tableinfo.parttype = classForm->parttype;
tableinfo.spcid = classForm->relnamespace;
tableinfo.relname = pstrdup(NameStr(classForm->relname));
tableinfo.autoinc_attnum = 0;
tableinfo.autoinc_consoid = 0;
tableinfo.autoinc_seqoid = 0;
ReleaseSysCache(tuple);
relname = quote_identifier(tableinfo.relname);
* Connect to SPI manager
*/
SPI_STACK_LOG("connect", NULL, NULL);
if (SPI_connect() != SPI_OK_CONNECT)
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE || tableinfo.relkind == RELKIND_STREAM) {
int i_srvname, i_ftoptions, i_ftwriteonly;
char* ft_write_only_str = NULL;
resetStringInfo(&query);
appendStringInfo(&query,
"SELECT fs.srvname, "
"pg_catalog.array_to_string(ARRAY("
"SELECT pg_catalog.quote_ident(option_name) || "
"' ' || pg_catalog.quote_literal(option_value) "
"FROM pg_catalog.pg_options_to_table(ftoptions) "
"ORDER BY option_name"
"), E',\n ') AS ftoptions, ft.ftwriteonly ftwriteonly "
"FROM pg_catalog.pg_foreign_table ft "
"JOIN pg_catalog.pg_foreign_server fs "
"ON (fs.oid = ft.ftserver) "
"WHERE ft.ftrelid = %u",
tableoid);
spirc = SPI_execute(query.data, true, INT_MAX);
proc = SPI_processed;
i_srvname = SPI_fnumber(SPI_tuptable->tupdesc, "srvname");
i_ftoptions = SPI_fnumber(SPI_tuptable->tupdesc, "ftoptions");
i_ftwriteonly = SPI_fnumber(SPI_tuptable->tupdesc, "ftwriteonly");
srvname = pstrdup(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_srvname));
ftoptions = pstrdup(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_ftoptions));
ft_write_only_str = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_ftwriteonly);
if (('t' == ft_write_only_str[0]) || ('1' == ft_write_only_str[0]) ||
(('o' == ft_write_only_str[0]) && ('n' == ft_write_only_str[1]))) {
ft_write_only = true;
}
if ((ftoptions != NULL && ftoptions[0]) && (NULL != strstr(ftoptions, "formatter"))) {
resetStringInfo(&query);
appendStringInfo(&query,
"WITH ft_options AS "
"(SELECT (pg_catalog.pg_options_to_table(ft.ftoptions)).option_name, "
"(pg_catalog.pg_options_to_table(ft.ftoptions)).option_value "
"FROM pg_catalog.pg_foreign_table ft "
"WHERE ft.ftrelid = %u) "
"SELECT option_value FROM ft_options WHERE option_name = 'formatter'",
tableoid);
spirc = SPI_execute(query.data, true, INT_MAX);
proc = SPI_processed;
int i_formatter = SPI_fnumber(SPI_tuptable->tupdesc, "option_value");
formatter = pstrdup(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i_formatter));
char* temp_iter = formatter;
cnt_ft_frmt_clmns = 1;
while (*temp_iter != '\0') {
if ('.' == *temp_iter)
cnt_ft_frmt_clmns++;
temp_iter++;
}
ft_frmt_clmn = (char**)pg_malloc(cnt_ft_frmt_clmns * sizeof(char*));
int iterf = 0;
temp_iter = formatter;
while (*temp_iter != '\0') {
ft_frmt_clmn[iterf] = temp_iter;
while (*temp_iter && '.' != *temp_iter)
temp_iter++;
if ('.' == *temp_iter) {
iterf++;
*temp_iter = '\0';
temp_iter++;
}
}
}
reltypename = "FOREIGN TABLE";
} else
reltypename = "TABLE";
if (NULL != tableinfo.reloptions && NULL != strstr(tableinfo.reloptions, "orientation=orc")) {
isHDFSTbl = true;
}
appendStringInfo(&buf, "SET search_path = %s;", quote_identifier(get_namespace_name(tableinfo.spcid)));
appendStringInfo(&buf, "\nCREATE %s%s %s",
(tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED) ?
"UNLOGGED " :
((tableinfo.relpersistence == RELPERSISTENCE_GLOBAL_TEMP) ? "GLOBAL TEMPORARY " : ""),
reltypename, relname);
if (classForm->reloftype != 0) {
relOfTypeName = get_typename(classForm->reloftype);
appendStringInfo(&buf, " of %s", relOfTypeName);
} else {
actual_atts = get_table_attribute(tableoid, &buf, formatter, ft_frmt_clmn, cnt_ft_frmt_clmns, &tableinfo);
* Fetch the constraint tuple from pg_constraint. There may be more than
* one match, because constraints are not required to have unique names;
* if so, error out.
*/
pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableoid));
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true, NULL, 1, skey);
while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);
if (con->contype == 'c' || con->contype == 'f') {
if (!con->convalidated) {
has_not_valid_check = true;
continue;
}
if (actual_atts == 0)
appendStringInfo(&buf, " (\n ");
else
appendStringInfo(&buf, ",\n ");
appendStringInfo(&buf, "CONSTRAINT %s ", quote_identifier(NameStr(con->conname)));
Oid conOid = HeapTupleGetOid(tuple);
appendStringInfo(&buf, "%s", pg_get_constraintdef_worker(conOid, false, 0));
actual_atts++;
} else if (tableinfo.autoinc_consoid == 0 &&
ConstraintSatisfyAutoIncrement(tuple, pg_constraint->rd_att, tableinfo.autoinc_attnum, con->contype)) {
tableinfo.autoinc_consoid = HeapTupleGetOid(tuple);
appendStringInfo(&buf, (actual_atts == 0) ? " (\n " : ",\n ");
appendStringInfo(&buf, "CONSTRAINT %s ", quote_identifier(NameStr(con->conname)));
appendStringInfo(&buf, "%s", pg_get_constraintdef_worker(tableinfo.autoinc_consoid, false, 0));
actual_atts++;
}
}
systable_endscan(scan);
heap_close(pg_constraint, AccessShareLock);
if (actual_atts) {
appendStringInfo(&buf, "\n)");
}
}
IsHDFSFTbl = append_table_info(tableinfo, srvname, &buf, tableoid, isHDFSTbl, &query, ftoptions, ft_write_only);
if (RELKIND_FOREIGN_TABLE == tableinfo.relkind || RELKIND_STREAM == tableinfo.relkind) {
* NOTICE: The distributeby clause is dumped when only a cordinator node
* is oprerated by gs_dump. For datanode, it is not necessary to dump
* distributeby clause.
*/
if (IsHDFSFTbl) {
char locator_type = GetLocatorType(tableoid);
switch (locator_type) {
case 'N':
appendStringInfo(&buf, "\nDISTRIBUTE BY ROUNDROBIN");
break;
case 'R':
appendStringInfo(&buf, "\nDISTRIBUTE BY REPLICATION");
break;
default:
break;
}
if (tableinfo.parttype == PARTTYPE_PARTITIONED_RELATION) {
get_table_partitiondef(&query, &buf, tableoid, tableinfo);
appendStringInfo(&buf, "AUTOMAPPED");
}
}
}
appendStringInfo(&buf, ";");
get_changing_table_info(tableoid, &buf, relname, tableinfo.relkind);
get_table_alter_info(tableinfo, has_not_valid_check, &buf, tableoid, relname);
* Disconnect from SPI manager
*/
SPI_STACK_LOG("finish", NULL, NULL);
if (SPI_finish() != SPI_OK_FINISH)
ereport(ERROR, (errcode(ERRCODE_SPI_FINISH_FAILURE), errmsg("SPI_finish failed")));
pfree_ext(query.data);
pfree_ext(tableinfo.relname);
return buf.data;
}
* @Description: get table's defination by table oid.
*/
Datum pg_get_tabledef_ext(PG_FUNCTION_ARGS)
{
Oid tableoid = PG_GETARG_OID(0);
char* tabledef = pg_get_tabledef_worker(tableoid);
if (tabledef != NULL)
PG_RETURN_TEXT_P(string_to_text(tabledef));
else
PG_RETURN_NULL();
}
* get_triggerdef - Get the definition of a trigger
* ----------
*/
Datum pg_get_triggerdef(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, false)));
}
Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, pretty)));
}
char* pg_get_triggerdef_string(Oid trigid)
{
return pg_get_triggerdef_worker(trigid, false);
}
static char* pg_get_triggerdef_worker(Oid trigid, bool pretty)
{
HeapTuple ht_trig;
Form_pg_trigger trigrec;
StringInfoData buf;
Relation tgrel;
ScanKeyData skey[1];
SysScanDesc tgscan;
int findx = 0;
char* tgname = NULL;
Datum value;
bool isnull = false;
char* tgfbody = NULL;
* Fetch the pg_trigger tuple by the Oid of the trigger
*/
tgrel = heap_open(TriggerRelationId, AccessShareLock);
ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(trigid));
tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true, NULL, 1, skey);
ht_trig = systable_getnext(tgscan);
if (!HeapTupleIsValid(ht_trig))
ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not find tuple for trigger %u", trigid)));
trigrec = (Form_pg_trigger)GETSTRUCT(ht_trig);
* Start the trigger definition. Note that the trigger's name should never
* be schema-qualified, but the trigger rel's name may be.
*/
initStringInfo(&buf);
tgname = NameStr(trigrec->tgname);
value = fastgetattr(ht_trig, Anum_pg_trigger_tgfbody, tgrel->rd_att, &isnull);
if (!isnull) {
tgfbody = TextDatumGetCString(value);
value = fastgetattr(ht_trig, Anum_pg_trigger_tgowner, tgrel->rd_att, &isnull);
if (DatumGetObjectId(value) != GetUserId()) {
appendStringInfo(&buf, "CREATE DEFINER = %s TRIGGER %s ", GetUserNameFromId(DatumGetObjectId(value)),
quote_identifier(tgname));
} else {
appendStringInfo(&buf, "CREATE TRIGGER %s ", quote_identifier(tgname));
}
} else {
if (OidIsValid(trigrec->tgconstraint)) {
appendStringInfo(&buf, "CREATE CONSTRAINT TRIGGER %s ", quote_identifier(tgname));
} else {
value = fastgetattr(ht_trig, Anum_pg_trigger_tgowner, tgrel->rd_att, &isnull);
if (DatumGetObjectId(value) != GetUserId()) {
appendStringInfo(&buf, "CREATE DEFINER = %s TRIGGER %s ", GetUserNameFromId(DatumGetObjectId(value)),
quote_identifier(tgname));
} else {
appendStringInfo(&buf, "CREATE TRIGGER %s ", quote_identifier(tgname));
}
}
}
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
appendStringInfo(&buf, "AFTER");
else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
appendStringInfo(&buf, "INSTEAD OF");
else
ereport(
ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unexpected tgtype value: %d", trigrec->tgtype)));
if (TRIGGER_FOR_INSERT(trigrec->tgtype)) {
appendStringInfo(&buf, " INSERT");
findx++;
}
if (TRIGGER_FOR_DELETE(trigrec->tgtype)) {
if (findx > 0)
appendStringInfo(&buf, " OR DELETE");
else
appendStringInfo(&buf, " DELETE");
findx++;
}
if (TRIGGER_FOR_UPDATE(trigrec->tgtype)) {
if (findx > 0)
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
findx++;
if (trigrec->tgattr.dim1 > 0) {
int i;
appendStringInfoString(&buf, " OF ");
for (i = 0; i < trigrec->tgattr.dim1; i++) {
char* attname = NULL;
if (i > 0)
appendStringInfoString(&buf, ", ");
attname = get_relid_attribute_name(trigrec->tgrelid, trigrec->tgattr.values[i]);
appendStringInfoString(&buf, quote_identifier(attname));
}
}
}
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype)) {
if (findx > 0)
appendStringInfo(&buf, " OR TRUNCATE");
else
appendStringInfo(&buf, " TRUNCATE");
findx++;
}
appendStringInfo(&buf, " ON %s ", generate_relation_name(trigrec->tgrelid, NIL));
if (OidIsValid(trigrec->tgconstraint)) {
if (OidIsValid(trigrec->tgconstrrelid))
appendStringInfo(&buf, "FROM %s ", generate_relation_name(trigrec->tgconstrrelid, NIL));
if (!trigrec->tgdeferrable)
appendStringInfo(&buf, "NOT ");
appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
if (trigrec->tginitdeferred)
appendStringInfo(&buf, "DEFERRED ");
else
appendStringInfo(&buf, "IMMEDIATE ");
}
if (TRIGGER_FOR_ROW(trigrec->tgtype))
appendStringInfo(&buf, "FOR EACH ROW ");
else
appendStringInfo(&buf, "FOR EACH STATEMENT ");
value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual, tgrel->rd_att, &isnull);
if (!isnull) {
Node* qual = NULL;
char relkind;
deparse_context context;
deparse_namespace dpns;
RangeTblEntry* oldrte = NULL;
RangeTblEntry* newrte = NULL;
appendStringInfoString(&buf, "WHEN (");
qual = (Node*)stringToNode(TextDatumGetCString(value));
relkind = get_rel_relkind(trigrec->tgrelid);
oldrte = makeNode(RangeTblEntry);
oldrte->rtekind = RTE_RELATION;
oldrte->relid = trigrec->tgrelid;
oldrte->relkind = relkind;
oldrte->eref = makeAlias("old", NIL);
oldrte->lateral = false;
oldrte->inh = false;
oldrte->inFromCl = true;
newrte = makeNode(RangeTblEntry);
newrte->rtekind = RTE_RELATION;
newrte->relid = trigrec->tgrelid;
newrte->relkind = relkind;
newrte->eref = makeAlias("new", NIL);
newrte->lateral = false;
newrte->inh = false;
newrte->inFromCl = true;
errno_t rc = memset_s(&dpns, sizeof(dpns), 0, sizeof(dpns));
securec_check(rc, "\0", "\0");
dpns.rtable = list_make2(oldrte, newrte);
dpns.ctes = NIL;
context.buf = &buf;
context.namespaces = list_make1(&dpns);
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = true;
context.prettyFlags = pretty ? PRETTYFLAG_PAREN : 0;
#ifdef PGXC
context.finalise_aggs = false;
context.sortgroup_colno = false;
context.parser_arg = NULL;
context.viewdef = false;
context.is_fqs = false;
#endif
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
context.qrw_phase = false;
context.is_upsert_clause = false;
get_rule_expr(qual, &context, false);
appendStringInfo(&buf, ") ");
}
if (tgfbody != NULL) {
bool isordernull = false;
bool isordernamenull = false;
char* tgordername = DatumGetCString(fastgetattr(ht_trig, Anum_pg_trigger_tgordername, tgrel->rd_att, &isordernamenull));
char* tgorder = DatumGetCString(fastgetattr(ht_trig, Anum_pg_trigger_tgorder, tgrel->rd_att, &isordernull));
if (!isordernull && !isordernamenull)
appendStringInfo(&buf, "%s %s ", tgorder, tgordername);
appendStringInfo(&buf, "%s;", tgfbody);
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
return buf.data;
}
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(", generate_function_name(trigrec->tgfoid, 0, NIL, NULL, false, NULL));
if (trigrec->tgnargs > 0) {
char* p = NULL;
int i;
value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs, tgrel->rd_att, &isnull);
if (isnull)
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("tgargs is null for trigger %u", trigid)));
p = (char*)VARDATA(DatumGetByteaP(value));
for (i = 0; i < trigrec->tgnargs; i++) {
if (i > 0)
appendStringInfo(&buf, ", ");
simple_quote_literal(&buf, p);
while (*p)
p++;
p++;
}
}
appendStringInfo(&buf, ")");
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
return buf.data;
}
* Pass back the TriggerWhen clause of a trigger given the pg_trigger record and
* the expression tree (in nodeToString() representation) from pg_trigger.tgqual
* for the trigger's WHEN condition.
*/
char* pg_get_trigger_whenclause(Form_pg_trigger trigrec, Node* whenClause, bool pretty)
{
StringInfoData buf;
char relkind;
deparse_context context;
deparse_namespace dpns;
RangeTblEntry *oldrte;
RangeTblEntry *newrte;
initStringInfo(&buf);
relkind = get_rel_relkind(trigrec->tgrelid);
oldrte = makeNode(RangeTblEntry);
oldrte->rtekind = RTE_RELATION;
oldrte->relid = trigrec->tgrelid;
oldrte->relkind = relkind;
oldrte->alias = makeAlias("old", NIL);
oldrte->eref = oldrte->alias;
oldrte->lateral = false;
oldrte->inh = false;
oldrte->inFromCl = true;
newrte = makeNode(RangeTblEntry);
newrte->rtekind = RTE_RELATION;
newrte->relid = trigrec->tgrelid;
newrte->relkind = relkind;
newrte->alias = makeAlias("new", NIL);
newrte->eref = newrte->alias;
newrte->lateral = false;
newrte->inh = false;
newrte->inFromCl = true;
errno_t rc = memset_s(&dpns, sizeof(dpns), 0, sizeof(dpns));
securec_check(rc, "\0", "\0");
dpns.rtable = list_make2(oldrte, newrte);
dpns.ctes = NIL;
context.buf = &buf;
context.namespaces = list_make1(&dpns);
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = true;
context.prettyFlags = GET_PRETTY_FLAGS(pretty);
#ifdef PGXC
context.finalise_aggs = false;
context.sortgroup_colno = false;
context.parser_arg = NULL;
#endif
context.viewdef = false;
context.is_fqs = false;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
context.qrw_phase = false;
context.is_upsert_clause = false;
get_rule_expr(whenClause, &context, false);
return buf.data;
}
* get_indexdef - Get the definition of an index
*
* In the extended version, there is a colno argument as well as pretty bool.
* if colno == 0, we want a complete index definition.
* if colno > 0, we only want the Nth index key's variable or expression.
*
* Note that the SQL-function versions of this omit any info about the
* index tablespace; this is intentional because pg_dump wants it that way.
* However pg_get_indexdef_string() includes the index tablespace.
* ----------
*/
Datum pg_get_indexdef(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0)));
}
* @Description: Get he definition of an index for dump scene.
* If the table is an interval partitioned table and the index is a local index and dumpSchemaOnly is true,
* only output indexes of range partitions, else the output is the same as pg_get_indexdef.
* @in The index table Oid and dump scheme only flag.
* @return Returns a palloc'd C string; no pretty-printing.
*/
Datum pg_get_indexdef_for_dump(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
bool dumpSchemaOnly = PG_GETARG_BOOL(1);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0, dumpSchemaOnly,
true, true)));
}
Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
int32 colno = PG_GETARG_INT32(1);
bool pretty = PG_GETARG_BOOL(2);
int prettyFlags;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno, NULL, colno != 0, true, prettyFlags,
false, false, false)));
}
* @Description: Internal version for use by ALTER TABLE.
* Includes a tablespace clause in the result.
* @in The index table Oid.
* @return Returns a palloc'd C string; no pretty-printing.
*/
char* pg_get_indexdef_string(Oid indexrelid)
{
return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0);
}
char* pg_get_indexdef_columns(Oid indexrelid, bool pretty)
{
int prettyFlags;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, prettyFlags);
}
static void AppendOnePartitionIndex(Oid indexRelId, Oid partOid, bool showTblSpc, bool *isFirst,
StringInfoData *buf, bool isSub = false)
{
Oid partIdxOid = getPartitionIndexOid(indexRelId, partOid);
HeapTuple partIdxHeapTuple = SearchSysCache1(PARTRELID, partIdxOid);
if (!HeapTupleIsValid(partIdxHeapTuple)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", partIdxOid)));
}
Form_pg_partition partIdxTuple = (Form_pg_partition)GETSTRUCT(partIdxHeapTuple);
if (*isFirst) {
*isFirst = false;
} else {
appendStringInfo(buf, ", ");
}
if (isSub) {
appendStringInfo(buf, "\n ");
appendStringInfo(buf, "SUBPARTITION %s", quote_identifier(partIdxTuple->relname.data));
} else {
appendStringInfo(buf, "PARTITION %s", quote_identifier(partIdxTuple->relname.data));
}
if (showTblSpc && OidIsValid(partIdxTuple->reltablespace)) {
char *tblspacName = get_tablespace_name(partIdxTuple->reltablespace);
if (tblspacName != NULL)
appendStringInfo(buf, " TABLESPACE %s", quote_identifier(tblspacName));
else
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace with OID %u does not exist", partIdxTuple->reltablespace)));
}
ReleaseSysCache(partIdxHeapTuple);
}
static void GetIndexdefForIntervalPartTabDumpSchemaOnly(Oid indexrelid, RangePartitionMap *partMap, bool showTblSpc,
StringInfoData *buf)
{
bool isFirst = true;
appendStringInfo(buf, " LOCAL");
appendStringInfoChar(buf, '(');
for (int i = 0; i < partMap->rangeElementsNum; ++i) {
RangeElement &elem = partMap->rangeElements[i];
if (elem.isInterval) {
continue;
}
AppendOnePartitionIndex(indexrelid, elem.partitionOid, showTblSpc, &isFirst, buf);
}
appendStringInfo(buf, ") ");
}
void pg_get_indexdef_partitions(Oid indexrelid, Form_pg_index idxrec, bool showTblSpc, StringInfoData *buf,
bool dumpSchemaOnly, bool showPartitionLocal, bool showSubpartitionLocal)
{
Oid relid = idxrec->indrelid;
* First of all, gs_dump will add share lock, no need to add lock here nor release it at the end of the function.
* Second, gs_dump supports option '--non-lock-table', in which case we should not add lock.
*/
Relation rel = heap_open(relid, NoLock);
if (dumpSchemaOnly && rel->partMap->type == PART_TYPE_INTERVAL) {
GetIndexdefForIntervalPartTabDumpSchemaOnly(indexrelid, (RangePartitionMap *)rel->partMap, showTblSpc, buf);
heap_close(rel, NoLock);
return;
}
appendStringInfo(buf, " LOCAL");
* The LOCAL index information of the partition and subpartition table is more.
* And the meta-statements (e.g. \d \d+ \dS) are used more.
* Therefore, when the meta-statement is called, the LOCAL index information is not displayed.
*/
bool isSub = RelationIsSubPartitioned(rel);
if ((!isSub && !showPartitionLocal) || (isSub && !showSubpartitionLocal)) {
heap_close(rel, NoLock);
return;
}
List *partList = NIL;
ListCell *lc1 = NULL;
ListCell *lc2 = NULL;
bool isFirst = true;
appendStringInfoChar(buf, '(');
if (isSub) {
int i = 0;
partList = RelationGetSubPartitionOidListList(rel);
int partListLen = list_length(partList);
appendStringInfo(buf, "\n");
foreach (lc1, partList) {
appendStringInfo(buf, " PARTITION partition_name(");
foreach (lc2, (List*)lfirst(lc1)) {
Oid partOid = DatumGetObjectId(lfirst(lc2));
AppendOnePartitionIndex(indexrelid, partOid, showTblSpc, &isFirst, buf, isSub);
}
appendStringInfo(buf, "\n )");
if (i != partListLen - 1) {
appendStringInfo(buf, ",\n");
}
isFirst = true;
i++;
}
appendStringInfo(buf, "\n");
} else {
partList = relationGetPartitionOidList(rel);
foreach (lc1, partList) {
Oid partOid = DatumGetObjectId(lfirst(lc1));
AppendOnePartitionIndex(indexrelid, partOid, showTblSpc, &isFirst, buf, isSub);
}
}
appendStringInfo(buf, ") ");
heap_close(rel, NoLock);
if (isSub) {
ReleaseSubPartitionOidList(&partList);
} else {
releasePartitionOidList(&partList);
}
}
* Internal workhorse to decompile an index definition.
*
* This is now used for exclusion constraints as well: if excludeOps is not
* NULL then it points to an array of exclusion operator OIDs.
*/
static char *pg_get_indexdef_worker(Oid indexrelid, int colno, const Oid *excludeOps, bool attrsOnly, bool showTblSpc,
int prettyFlags, bool dumpSchemaOnly, bool showPartitionLocal, bool showSubpartitionLocal)
{
bool isConstraint = (excludeOps != NULL);
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
Form_pg_index idxrec;
Form_pg_class idxrelrec;
Form_pg_am amrec;
List* indexprs = NIL;
ListCell* indexpr_item = NULL;
List* context = NIL;
Oid indrelid;
int keyno;
Datum indcollDatum;
Datum indclassDatum;
Datum indoptionDatum;
bool isnull = false;
oidvector* indcollation = NULL;
oidvector* indclass = NULL;
int2vector* indoption = NULL;
StringInfoData buf;
char* str = NULL;
char* sep = NULL;
int indnkeyatts;
* Fetch the pg_index tuple by the Oid of the index
*/
ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
if (!HeapTupleIsValid(ht_idx))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexrelid)));
idxrec = (Form_pg_index)GETSTRUCT(ht_idx);
indrelid = idxrec->indrelid;
Assert(indexrelid == idxrec->indexrelid);
indnkeyatts = GetIndexKeyAttsByTuple(NULL, ht_idx);
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))
ereport(
ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %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))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for access method %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;
bool isnull = false;
char* exprsString = NULL;
exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indexprs, &isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
indexprs = (List*)stringToNode(exprsString);
pfree_ext(exprsString);
} else
indexprs = NIL;
indexpr_item = list_head(indexprs);
context = deparse_context_for(get_relation_name(indrelid), indrelid);
* Start the index definition. Note that the index's name should never be
* schema-qualified, but the indexed rel's name may be.
*/
initStringInfo(&buf);
if (!attrsOnly) {
if (!isConstraint)
appendStringInfo(&buf,
"CREATE %sINDEX %s ON %s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
else
appendStringInfo(&buf, "EXCLUDE USING %s (", quote_identifier(NameStr(amrec->amname)));
}
* Report the indexed attributes
*/
sep = "";
for (keyno = 0; keyno < idxrec->indnatts; keyno++) {
AttrNumber attnum = idxrec->indkey.values[keyno];
int16 opt = indoption->values[keyno];
Oid keycoltype;
Oid keycolcollation;
* Ignore non-key attributes if told to.
*/
if (keyno >= indnkeyatts) {
break;
}
if (!colno)
appendStringInfoString(&buf, sep);
sep = ", ";
if (attnum != 0) {
char* attname = NULL;
int32 keycoltypmod;
attname = get_relid_attribute_name(indrelid, attnum);
if (!colno || colno == keyno + 1)
appendStringInfoString(&buf, quote_identifier(attname));
get_atttypetypmodcoll(indrelid, attnum, &keycoltype, &keycoltypmod, &keycolcollation);
} else {
Node* indexkey = NULL;
if (indexpr_item == NULL)
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("too few entries in indexprs list")));
indexkey = (Node*)lfirst(indexpr_item);
indexpr_item = lnext(indexpr_item);
str = deparse_expression_pretty(indexkey, context, false, false, prettyFlags, 0);
if (!colno || colno == keyno + 1) {
if (indexkey &&
((IsA(indexkey, FuncExpr) && ((FuncExpr*)indexkey)->funcformat == COERCE_EXPLICIT_CALL) ||
IsA(indexkey, PrefixKey)))
appendStringInfoString(&buf, str);
else
appendStringInfo(&buf, "(%s)", str);
}
keycoltype = exprType(indexkey);
keycolcollation = exprCollation(indexkey);
}
if (!attrsOnly && (!colno || colno == keyno + 1)) {
Oid indcoll;
if (keyno >= indnkeyatts) {
continue;
}
indcoll = indcollation->values[keyno];
if (OidIsValid(indcoll) && indcoll != keycolcollation)
appendStringInfo(&buf, " COLLATE %s", generate_collation_name((indcoll)));
get_opclass_name(indclass->values[keyno], keycoltype, &buf);
if (amrec->amcanorder) {
if (opt & INDOPTION_DESC) {
appendStringInfo(&buf, " DESC");
if (!(opt & INDOPTION_NULLS_FIRST))
appendStringInfo(&buf, " NULLS LAST");
} else {
if (opt & INDOPTION_NULLS_FIRST)
appendStringInfo(&buf, " NULLS FIRST");
}
}
if (excludeOps != NULL)
appendStringInfo(&buf, " WITH %s", generate_operator_name(excludeOps[keyno], keycoltype, keycoltype));
}
}
if (!attrsOnly) {
appendStringInfoChar(&buf, ')');
if (idxrelrec->parttype == PARTTYPE_PARTITIONED_RELATION &&
idxrelrec->relkind != RELKIND_GLOBAL_INDEX) {
pg_get_indexdef_partitions(indexrelid, idxrec, showTblSpc, &buf, dumpSchemaOnly,
showPartitionLocal, showSubpartitionLocal);
}
* If it has options, append "WITH (options)"
*/
str = flatten_reloptions(indexrelid);
if (str != NULL) {
appendStringInfo(&buf, " WITH (%s)", str);
pfree_ext(str);
}
* Print tablespace, but only if requested.
*/
if (showTblSpc) {
Oid tblspc;
tblspc = get_rel_tablespace(indexrelid);
if (!OidIsValid(tblspc)) {
tblspc = u_sess->proc_cxt.MyDatabaseTableSpace;
}
if (isConstraint) {
appendStringInfoString(&buf, " USING INDEX");
}
char* tblspacName = get_tablespace_name(tblspc);
if (tblspacName != NULL)
appendStringInfo(&buf, " TABLESPACE %s", quote_identifier(tblspacName));
else
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace with OID %u does not exist", tblspc)));
}
if (!GetIndexVisibleStateByTuple(ht_idx)) {
appendStringInfo(&buf, " INVISIBLE");
}
* If it's a partial index, decompile and append the predicate
*/
if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) {
Node* node = NULL;
Datum predDatum;
bool isnull = false;
char* predString = NULL;
predDatum = SysCacheGetAttr(INDEXRELID, ht_idx, Anum_pg_index_indpred, &isnull);
Assert(!isnull);
predString = TextDatumGetCString(predDatum);
node = (Node*)stringToNode(predString);
pfree_ext(predString);
str = deparse_expression_pretty(node, context, false, false, prettyFlags, 0);
if (isConstraint)
appendStringInfo(&buf, " WHERE (%s)", str);
else
appendStringInfo(&buf, " WHERE %s", str);
}
}
ReleaseSysCache(ht_idx);
ReleaseSysCache(ht_idxrel);
ReleaseSysCache(ht_am);
return buf.data;
}
* pg_get_constraintdef
*
* Returns the definition for the constraint, ie, everything that needs to
* appear after "ALTER TABLE ... ADD CONSTRAINT <constraintname>".
*/
Datum pg_get_constraintdef(PG_FUNCTION_ARGS)
{
Oid constraintId = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId, false, 0)));
}
Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
{
Oid constraintId = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId, false, prettyFlags)));
}
char* pg_get_constraintdef_string(Oid constraintId)
{
return pg_get_constraintdef_worker(constraintId, true, 0);
}
char* pg_get_constraintdef_part_string(Oid constraintId)
{
return pg_get_constraintdef_worker(constraintId, false, 0, true);
}
static char* pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags, bool with_option)
{
HeapTuple tup;
Form_pg_constraint conForm;
StringInfoData buf;
* Fetch the pg_constraint tuple by the Oid of the constraint
*/
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintId));
if (!HeapTupleIsValid(tup))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for constraint %u", constraintId)));
conForm = (Form_pg_constraint)GETSTRUCT(tup);
initStringInfo(&buf);
if (fullCommand && OidIsValid(conForm->conrelid)) {
appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
generate_relation_name(conForm->conrelid, NIL), quote_identifier(NameStr(conForm->conname)));
}
switch (conForm->contype) {
case CONSTRAINT_FOREIGN: {
Datum val;
bool isnull = false;
const char* string = NULL;
appendStringInfo(&buf, "FOREIGN KEY (");
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conkey, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conkey for constraint %u", constraintId)));
decompile_column_index_array(val, conForm->conrelid, &buf);
appendStringInfo(&buf, ") REFERENCES %s(", generate_relation_name(conForm->confrelid, NIL));
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_confkey, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null confkey for constraint %u", constraintId)));
decompile_column_index_array(val, conForm->confrelid, &buf);
appendStringInfo(&buf, ")");
switch (conForm->confmatchtype) {
case FKCONSTR_MATCH_FULL:
string = " MATCH FULL";
break;
case FKCONSTR_MATCH_PARTIAL:
string = " MATCH PARTIAL";
break;
case FKCONSTR_MATCH_UNSPECIFIED:
string = "";
break;
default:
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized confmatchtype: %d", conForm->confmatchtype)));
string = "";
break;
}
appendStringInfoString(&buf, string);
switch (conForm->confupdtype) {
case FKCONSTR_ACTION_NOACTION:
string = NULL;
break;
case FKCONSTR_ACTION_RESTRICT:
string = "RESTRICT";
break;
case FKCONSTR_ACTION_CASCADE:
string = "CASCADE";
break;
case FKCONSTR_ACTION_SETNULL:
string = "SET NULL";
break;
case FKCONSTR_ACTION_SETDEFAULT:
string = "SET DEFAULT";
break;
default:
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized confupdtype: %d", conForm->confupdtype)));
string = NULL;
break;
}
if (string != NULL)
appendStringInfo(&buf, " ON UPDATE %s", string);
switch (conForm->confdeltype) {
case FKCONSTR_ACTION_NOACTION:
string = NULL;
break;
case FKCONSTR_ACTION_RESTRICT:
string = "RESTRICT";
break;
case FKCONSTR_ACTION_CASCADE:
string = "CASCADE";
break;
case FKCONSTR_ACTION_SETNULL:
string = "SET NULL";
break;
case FKCONSTR_ACTION_SETDEFAULT:
string = "SET DEFAULT";
break;
default:
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized confdeltype: %d", conForm->confdeltype)));
string = NULL;
break;
}
if (string != NULL)
appendStringInfo(&buf, " ON DELETE %s", string);
break;
}
case CONSTRAINT_PRIMARY:
case CONSTRAINT_UNIQUE: {
Datum val;
bool isnull = false;
Oid indexId;
HeapTuple tup_idx;
Form_pg_index idxForm;
Oid indexrelid_con;
Oid indrelid;
List* indexprs = NIL;
ListCell* indexpr_item = NULL;
List* context = NIL;
const char* sep = NULL;
char* str = NULL;
int indnkeyatts;
Datum indoptionDatum;
int2vector* indoption = NULL;
indexrelid_con = conForm->conindid;
tup_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid_con));
if (!HeapTupleIsValid(tup_idx)) {
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexrelid_con)));
}
idxForm = (Form_pg_index)GETSTRUCT(tup_idx);
if (conForm->consoft) {
if (conForm->conopt) {
appendStringInfo(&buf, "enable optimization Informational Constraint ");
} else {
appendStringInfo(&buf, "disable optimization Informational Constraint ");
}
}
if (conForm->contype == CONSTRAINT_PRIMARY)
appendStringInfo(&buf, "PRIMARY KEY (");
else
appendStringInfo(&buf, "UNIQUE (");
* Fetch the pg_constraint tuple by the Oid(conForm->conindid) of the index
*/
indrelid = idxForm->indrelid;
indnkeyatts = GetIndexKeyAttsByTuple(NULL, tup_idx);
indoptionDatum = SysCacheGetAttr(INDEXRELID, tup_idx, Anum_pg_index_indoption, &isnull);
Assert(!isnull);
indoption = (int2vector*)DatumGetPointer(indoptionDatum);
if (!heap_attisnull(tup_idx, Anum_pg_index_indexprs, NULL)) {
Datum exprsDatum;
isnull = false;
char* exprsString = NULL;
exprsDatum = SysCacheGetAttr(INDEXRELID, tup_idx, Anum_pg_index_indexprs, &isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
indexprs = (List*)stringToNode(exprsString);
pfree_ext(exprsString);
} else {
indexprs = NIL;
}
indexpr_item = list_head(indexprs);
context = deparse_context_for(get_relation_name(indrelid), indrelid);
sep = "";
for (int keyno = 0; keyno < idxForm->indnatts; keyno++) {
AttrNumber attnum = idxForm->indkey.values[keyno];
int16 opt = indoption->values[keyno];
if (keyno >= indnkeyatts) {
break;
}
appendStringInfoString(&buf, sep);
sep = ", ";
if (attnum != 0) {
char *attname = NULL;
attname = get_relid_attribute_name(indrelid, attnum);
appendStringInfoString(&buf, quote_identifier(attname));
} else {
Node* indexkey = NULL;
if (indexpr_item == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("too few entries in indexprs list")));
indexkey = (Node*)lfirst(indexpr_item);
indexpr_item = lnext(indexpr_item);
str = deparse_expression_pretty(indexkey, context, false, false,
PRETTYFLAG_PAREN | PRETTYFLAG_INDENT, 0);
if (IsA(indexkey, PrefixKey)) {
appendStringInfo(&buf, "%s", str);
} else {
appendStringInfo(&buf, "(%s)", str);
}
}
if (opt & INDOPTION_DESC) {
appendStringInfo(&buf, " DESC");
}
}
appendStringInfo(&buf, ")");
isnull = true;
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conincluding, &isnull);
if (!isnull) {
appendStringInfoString(&buf, " INCLUDE (");
decompile_column_index_array(val, conForm->conrelid, &buf);
appendStringInfoChar(&buf, ')');
}
indexId = get_constraint_index(constraintId);
if ((fullCommand || with_option) && OidIsValid(indexId)) {
char* options = flatten_reloptions(indexId);
Oid tblspc;
if (options != NULL) {
appendStringInfo(&buf, " WITH (%s)", options);
pfree_ext(options);
}
tblspc = get_rel_tablespace(indexId);
if (OidIsValid(tblspc)) {
char* tblspcName = get_tablespace_name(tblspc);
if (tblspcName != NULL)
appendStringInfo(&buf, " USING INDEX TABLESPACE %s", quote_identifier(tblspcName));
else
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("tablespace with OID %u does not exist", tblspc)));
}
}
ReleaseSysCache(tup_idx);
break;
}
case CONSTRAINT_CHECK: {
Datum val;
bool isnull = false;
char* conbin = NULL;
char* consrc = NULL;
Node* expr = NULL;
List* context = NIL;
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conbin, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conbin for constraint %u", constraintId)));
conbin = TextDatumGetCString(val);
expr = (Node*)stringToNode(conbin);
if (conForm->conrelid != InvalidOid) {
context = deparse_context_for(get_relation_name(conForm->conrelid), conForm->conrelid);
} else {
context = NIL;
}
consrc = deparse_expression_pretty(expr, context, false, false, prettyFlags, 0);
* Now emit the constraint definition, adding NO INHERIT if
* necessary.
*
* There are cases where the constraint expression will be
* fully parenthesized and we don't need the outer parens ...
* but there are other cases where we do need 'em. Be
* conservative for now.
*
* Note that simply checking for leading '(' and trailing ')'
* would NOT be good enough, consider "(x > 0) AND (y > 0)".
*/
appendStringInfo(&buf, "CHECK (%s)%s", consrc, conForm->connoinherit ? " NO INHERIT" : "");
bool isNull = true;
Relation pg_constraint;
pg_constraint = heap_open(ConstraintRelationId, NoLock);
Datum datum = heap_getattr(tup, Anum_pg_constraint_condisable, RelationGetDescr(pg_constraint), &isNull);
bool condisable = DatumGetBool(datum);
if (condisable)
appendStringInfo(&buf, " DISABLE");
heap_close(pg_constraint, NoLock);
break;
}
case CONSTRAINT_TRIGGER:
* There isn't an ALTER TABLE syntax for creating a user-defined
* constraint trigger, but it seems better to print something than
* throw an error; if we throw error then this function couldn't
* safely be applied to all rows of pg_constraint.
*/
appendStringInfo(&buf, "TRIGGER");
break;
case CONSTRAINT_EXCLUSION: {
Oid indexOid = conForm->conindid;
Datum val;
bool isnull = false;
Datum* elems = NULL;
int nElems;
int i;
Oid* operators = NULL;
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conexclop, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conexclop for constraint %u", constraintId)));
deconstruct_array(DatumGetArrayTypeP(val), OIDOID, sizeof(Oid), true, 'i', &elems, NULL, &nElems);
operators = (Oid*)palloc(nElems * sizeof(Oid));
for (i = 0; i < nElems; i++)
operators[i] = DatumGetObjectId(elems[i]);
appendStringInfoString(&buf, pg_get_indexdef_worker(indexOid, 0, operators, false, false, prettyFlags));
break;
}
case CONSTRAINT_CLUSTER: {
Datum val;
bool isnull = false;
appendStringInfo(&buf, "PARTIAL CLUSTER KEY (");
val = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conkey, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conkey for constraint %u", constraintId)));
decompile_column_index_array(val, conForm->conrelid, &buf);
appendStringInfo(&buf, ")");
break;
}
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("invalid constraint type \"%c\"", conForm->contype)));
break;
}
if (conForm->condeferrable)
appendStringInfo(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(&buf, " INITIALLY DEFERRED");
if (!conForm->convalidated)
appendStringInfoString(&buf, " NOT VALID");
ReleaseSysCache(tup);
return buf.data;
}
* Convert an int16[] Datum into a comma-separated list of column names
* for the indicated relation; append the list to buf.
*/
static void decompile_column_index_array(Datum column_index_array, Oid relId, StringInfo buf)
{
Datum* keys = NULL;
int nKeys;
int j;
deconstruct_array(DatumGetArrayTypeP(column_index_array), INT2OID, 2, true, 's', &keys, NULL, &nKeys);
for (j = 0; j < nKeys; j++) {
char* colName = NULL;
colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
if (j == 0)
appendStringInfoString(buf, quote_identifier(colName));
else
appendStringInfo(buf, ", %s", quote_identifier(colName));
}
}
* get_expr - Decompile an expression tree
*
* Input: an expression tree in nodeToString form, and a relation OID
*
* Output: reverse-listed expression
*
* Currently, the expression can only refer to a single relation, namely
* the one specified by the second parameter. This is sufficient for
* partial indexes, column default expressions, etc. We also support
* Var-free expressions, for which the OID can be InvalidOid.
* ----------
*/
Datum pg_get_expr(PG_FUNCTION_ARGS)
{
text* expr = PG_GETARG_TEXT_P(0);
Oid relid = PG_GETARG_OID(1);
char* relname = NULL;
if (OidIsValid(relid)) {
relname = get_rel_name(relid);
* If the OID isn't actually valid, don't throw an error, just return
* NULL. This is a bit questionable, but it's what we've done
* historically, and it can help avoid unwanted failures when
* examining catalog entries for just-deleted relations.
*/
if (relname == NULL)
PG_RETURN_NULL();
} else
relname = NULL;
PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, 0));
}
Datum pg_get_expr_ext(PG_FUNCTION_ARGS)
{
text* expr = PG_GETARG_TEXT_P(0);
Oid relid = PG_GETARG_OID(1);
bool pretty = PG_GETARG_BOOL(2);
int prettyFlags;
char* relname = NULL;
prettyFlags = pretty ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT) : 0;
if (OidIsValid(relid)) {
relname = get_rel_name(relid);
if (relname == NULL)
PG_RETURN_NULL();
} else
relname = NULL;
PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, prettyFlags));
}
static text* pg_get_expr_worker(text* expr, Oid relid, const char* relname, int prettyFlags)
{
Node* node = NULL;
List* context = NIL;
char* exprstr = NULL;
char* str = NULL;
exprstr = text_to_cstring(expr);
node = (Node*)stringToNode_skip_extern_fields(exprstr);
pfree_ext(exprstr);
if (OidIsValid(relid))
context = deparse_context_for(relname, relid);
else
context = NIL;
str = deparse_expression_pretty(node, context, false, false, prettyFlags, 0);
return string_to_text(str);
}
static inline bool IsUserVisible(Oid roleid)
{
StringInfoData query;
initStringInfo(&query);
appendStringInfo(&query, "select oid from %s where oid = %u", "pg_catalog.pg_roles", roleid);
Oid oid = SearchSysTable(query.data);
pfree_ext(query.data);
return OidIsValid(oid);
}
* get_userbyid - Get a user name by roleid and
* fallback to 'unknown (OID=n)'
* ----------
*/
Datum pg_get_userbyid(PG_FUNCTION_ARGS)
{
Oid roleid = PG_GETARG_OID(0);
Name result;
HeapTuple roletup;
Form_pg_authid role_rec;
errno_t rc;
if (!IsUserVisible(roleid)) {
PG_RETURN_NULL();
}
* Allocate space for the result
*/
result = (Name)palloc(NAMEDATALEN);
rc = memset_s(NameStr(*result), NAMEDATALEN, 0, NAMEDATALEN);
securec_check(rc, "\0", "\0");
* Get the pg_authid entry and print the result
*/
roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (HeapTupleIsValid(roletup)) {
role_rec = (Form_pg_authid)GETSTRUCT(roletup);
rc = strncpy_s(NameStr(*result), NAMEDATALEN, NameStr(role_rec->rolname), NAMEDATALEN - 1);
securec_check(rc, "\0", "\0");
ReleaseSysCache(roletup);
} else {
rc = sprintf_s(NameStr(*result), NAMEDATALEN, "unknown (OID=%u)", roleid);
securec_check_ss(rc, "\0", "\0");
}
PG_RETURN_NAME(result);
}
* pg_check_authid - Check a user by roleid whether exist
* ----------
*/
Datum pg_check_authid(PG_FUNCTION_ARGS)
{
Oid authid = PG_GETARG_OID(0);
HeapTuple roletup;
* Get the pg_authid entry and print the result
*/
roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(authid));
if (HeapTupleIsValid(roletup)) {
ReleaseSysCache(roletup);
PG_RETURN_BOOL(true);
} else {
PG_RETURN_BOOL(false);
}
}
* pg_get_serial_sequence
* Get the name of the sequence used by a serial column,
* formatted suitably for passing to setval, nextval or currval.
* First parameter is not treated as double-quoted, second parameter
* is --- see documentation for reason.
*/
Datum pg_get_serial_sequence(PG_FUNCTION_ARGS)
{
text* tablename = PG_GETARG_TEXT_P(0);
text* columnname = PG_GETARG_TEXT_PP(1);
RangeVar* tablerv = NULL;
Oid tableOid;
char* column = NULL;
AttrNumber attnum;
Oid sequenceId = InvalidOid;
Relation depRel;
ScanKeyData key[3];
SysScanDesc scan;
HeapTuple tup;
List* names = NIL;
names = textToQualifiedNameList(tablename);
tablerv = makeRangeVarFromNameList(names);
tableOid = RangeVarGetRelid(tablerv, NoLock, false);
column = text_to_cstring(columnname);
attnum = get_attnum(tableOid, column);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist", column, tablerv->relname)));
depRel = heap_open(DependRelationId, AccessShareLock);
ScanKeyInit(
&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableOid));
ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 3, key);
while (HeapTupleIsValid(tup = systable_getnext(scan))) {
Form_pg_depend deprec = (Form_pg_depend)GETSTRUCT(tup);
* We assume any auto dependency of a sequence on a column must be
* what we are looking for. (We need the relkind test because indexes
* can also have auto dependencies on columns.)
*/
if (deprec->classid == RelationRelationId && deprec->objsubid == 0 && deprec->deptype == DEPENDENCY_AUTO &&
RELKIND_IS_SEQUENCE(get_rel_relkind(deprec->objid))) {
sequenceId = deprec->objid;
break;
}
}
systable_endscan(scan);
heap_close(depRel, AccessShareLock);
if (OidIsValid(sequenceId)) {
HeapTuple classtup;
Form_pg_class classtuple;
char* nspname = NULL;
char* result = NULL;
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
if (!HeapTupleIsValid(classtup))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", sequenceId)));
classtuple = (Form_pg_class)GETSTRUCT(classtup);
nspname = get_namespace_name(classtuple->relnamespace);
if (nspname == NULL)
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for namespace %u", classtuple->relnamespace)));
result = quote_qualified_identifier(nspname, NameStr(classtuple->relname));
ReleaseSysCache(classtup);
list_free_ext(names);
PG_RETURN_TEXT_P(string_to_text(result));
}
list_free_ext(names);
PG_RETURN_NULL();
}
static inline bool IsFunctionVisible(Oid funcoid)
{
StringInfoData query;
initStringInfo(&query);
appendStringInfo(&query, "select pg_namespace.oid from pg_catalog.pg_namespace where pg_namespace.oid in "
"(select pronamespace from pg_catalog.pg_proc where pg_proc.oid = %u);", funcoid);
Oid oid = SearchSysTable(query.data);
pfree_ext(query.data);
return OidIsValid(oid);
}
* pg_get_functiondef
* Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
* the specified function.
*
* Note: if you change the output format of this function, be careful not
* to break psql's rules (in \ef and \sf) for identifying the start of the
* function body. To wit: the function body starts on a line that begins
* with "AS ", and no preceding line will look like that.
*/
Datum pg_get_functiondef(PG_FUNCTION_ARGS)
{
Oid funcid = PG_GETARG_OID(0);
if (!IsFunctionVisible(funcid)) {
PG_RETURN_NULL();
}
char* funcdef = NULL;
TupleDesc tupdesc;
HeapTuple tuple;
Datum values[2];
bool nulls[2];
int headerlines = 0;
errno_t rc = EOK;
tupdesc = CreateTemplateTupleDesc(2, false);
TupleDescInitEntry(tupdesc, (AttrNumber)1, "headerlines", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)2, "definition", TEXTOID, -1, 0);
(void)BlessTupleDesc(tupdesc);
rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
securec_check_c(rc, "\0", "\0");
funcdef = pg_get_functiondef_worker(funcid, &headerlines);
values[0] = Int32GetDatum((int32)(headerlines));
values[1] = PointerGetDatum(string_to_text(funcdef));
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
char* pg_get_functiondef_string(Oid funcid)
{
char* funcdef = NULL;
int headerlines = 0;
funcdef = pg_get_functiondef_worker(funcid, &headerlines);
return funcdef;
}
* pg_get_function_arguments
* Get a nicely-formatted list of arguments for a function.
* This is everything that would go between the parentheses in
* CREATE FUNCTION.
*/
Datum pg_get_function_arguments(PG_FUNCTION_ARGS)
{
Oid funcid = PG_GETARG_OID(0);
if (!IsFunctionVisible(funcid)) {
PG_RETURN_NULL();
}
StringInfoData buf;
HeapTuple proctup;
initStringInfo(&buf);
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcid)));
(void)print_function_arguments(&buf, proctup, false, true);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
}
* pg_get_function_identity_arguments
* Get a formatted list of arguments for a function.
* This is everything that would go between the parentheses in
* ALTER FUNCTION, etc. In particular, don't print defaults.
*/
Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS)
{
Oid funcid = PG_GETARG_OID(0);
if (!IsFunctionVisible(funcid)) {
PG_RETURN_NULL();
}
StringInfoData buf;
HeapTuple proctup;
initStringInfo(&buf);
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcid)));
(void)print_function_arguments(&buf, proctup, false, false);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
}
* pg_get_function_result
* Get a nicely-formatted version of the result type of a function.
* This is what would appear after RETURNS in CREATE FUNCTION.
*/
Datum pg_get_function_result(PG_FUNCTION_ARGS)
{
Oid funcid = PG_GETARG_OID(0);
if (!IsFunctionVisible(funcid)) {
PG_RETURN_NULL();
}
StringInfoData buf;
HeapTuple proctup;
initStringInfo(&buf);
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcid)));
print_function_rettype(&buf, proctup);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
}
char* pg_get_functiondef_worker(Oid funcid, int* headerlines)
{
StringInfoData buf;
StringInfoData dq;
HeapTuple proctup;
HeapTuple langtup;
Form_pg_proc proc;
Form_pg_language lang;
Datum tmp;
bool isnull = false;
const char* prosrc = NULL;
const char* name = NULL;
const char* nsp = NULL;
float4 procost;
int oldlen;
char* p = NULL;
bool isOraFunc = false;
char* pkgname = NULL;
char* typname = NULL;
initStringInfo(&buf);
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcid)));
proc = (Form_pg_proc)GETSTRUCT(proctup);
name = NameStr(proc->proname);
if (proc->proisagg)
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("\"%s\" is an aggregate function", name)));
langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
if (!HeapTupleIsValid(langtup))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for language %u", proc->prolang)));
lang = (Form_pg_language)GETSTRUCT(langtup);
* We always qualify the function name, to ensure the right function gets
* replaced.
*/
nsp = get_namespace_name(proc->pronamespace);
bool proIsProcedure = false;
if (t_thrd.proc->workingVersionNum >= STP_SUPPORT_COMMIT_ROLLBACK) {
Datum datum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prokind, &isnull);
proIsProcedure = isnull ? false : PROC_IS_PRO(CharGetDatum(datum));
}
bool ispackage = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_package, &isnull);
Datum packageOidDatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_packageid, &isnull);
if (!isnull && ispackage)
{
Oid packageoid = DatumGetObjectId(packageOidDatum);
if (OidIsValid(packageoid)) {
pkgname = GetPackageName(packageoid);
}
}
if (!isnull && !ispackage) {
Oid typeoid = DatumGetObjectId(packageOidDatum);
if (OidIsValid(typeoid)) {
typname = get_typename(typeoid);
}
}
if (proIsProcedure) {
if (pkgname != NULL) {
appendStringInfo(&buf, "CREATE OR REPLACE PROCEDURE %s(",
quote_qualified_identifier(nsp, pkgname, name));
} else if (typname != NULL) {
appendStringInfo(&buf, "CREATE OR REPLACE PROCEDURE %s(",
quote_qualified_identifier(nsp, typname, name));
} else if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
appendStringInfo(&buf, "CREATE DEFINER = %s PROCEDURE %s(",
GetUserNameFromId(proc->proowner), quote_qualified_identifier(nsp, name));
} else {
appendStringInfo(&buf, "CREATE OR REPLACE PROCEDURE %s(",
quote_qualified_identifier(nsp, name));
}
} else {
if (pkgname != NULL) {
appendStringInfo(&buf, "CREATE OR REPLACE FUNCTION %s(",
quote_qualified_identifier(nsp, pkgname, name));
} else if (typname != NULL) {
appendStringInfo(&buf, "CREATE OR REPLACE FUNCTION %s(",
quote_qualified_identifier(nsp, typname, name));
} else if (u_sess->attr.attr_sql.sql_compatibility == B_FORMAT) {
appendStringInfo(&buf, "CREATE DEFINER = %s FUNCTION %s(",
GetUserNameFromId(proc->proowner), quote_qualified_identifier(nsp, name));
} else {
appendStringInfo(&buf, "CREATE OR REPLACE FUNCTION %s(",
quote_qualified_identifier(nsp, name));
}
}
if (t_thrd.proc->workingVersionNum >= COMMENT_PROC_VERSION_NUM) {
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargsrc, &isnull);
if (isnull) {
(void)print_function_arguments(&buf, proctup, false, true);
} else {
print_function_ora_arguments(&buf, proctup);
isOraFunc = true;
}
} else {
(void)print_function_arguments(&buf, proctup, false, true);
}
if (!proIsProcedure) {
if (!isOraFunc) {
appendStringInfoString(&buf, ")\n RETURNS ");
} else {
appendStringInfoString(&buf, ")\n RETURN ");
}
} else {
appendStringInfoString(&buf, ")\n");
}
if (!proIsProcedure) {
print_function_rettype(&buf, proctup);
if (!isOraFunc) {
appendStringInfo(&buf, "\n LANGUAGE %s\n", quote_identifier(NameStr(lang->lanname)));
}
}
oldlen = buf.len;
if (proc->proiswindow)
appendStringInfoString(&buf, " WINDOW");
switch (proc->provolatile) {
case PROVOLATILE_IMMUTABLE:
appendStringInfoString(&buf, " IMMUTABLE");
break;
case PROVOLATILE_STABLE:
appendStringInfoString(&buf, " STABLE");
break;
case PROVOLATILE_VOLATILE:
break;
default:
break;
}
if (proc->proisstrict)
appendStringInfoString(&buf, " STRICT");
if (PLSQL_SECURITY_DEFINER) {
if (proc->prosecdef) {
appendStringInfoString(&buf, " AUTHID DEFINER");
} else {
appendStringInfoString(&buf, " AUTHID CURRENT_USER");
}
} else {
if (proc->prosecdef)
appendStringInfoString(&buf, " SECURITY DEFINER");
}
if (proc->proleakproof)
appendStringInfoString(&buf, " LEAKPROOF");
Datum fenced = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_fenced, &isnull);
if (!isnull && DatumGetBool(fenced))
appendStringInfoString(&buf, " FENCED");
else if (!proIsProcedure)
appendStringInfoString(&buf, " NOT FENCED");
Datum shippable = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_shippable, &isnull);
if (!isnull && DatumGetBool(shippable))
appendStringInfoString(&buf, " SHIPPABLE");
else if (!proIsProcedure)
appendStringInfoString(&buf, " NOT SHIPPABLE");
int2 parallelCursorSeq = GetParallelCursorSeq(funcid);
if (parallelCursorSeq != -1) {
print_parallel_enable(&buf, proctup, parallelCursorSeq, funcid);
}
Datum propackage = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_package, &isnull);
if (!isnull && DatumGetBool(propackage))
appendStringInfoString(&buf, " PACKAGE");
if (proc->prolang == INTERNALlanguageId || proc->prolang == ClanguageId)
procost = 1;
else
procost = 100;
if (proc->procost != procost)
appendStringInfo(&buf, " COST %g", proc->procost);
if (proc->prorows > 0 && proc->prorows != 1000)
appendStringInfo(&buf, " ROWS %g", proc->prorows);
if (oldlen != buf.len)
appendStringInfoChar(&buf, '\n');
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proconfig, &isnull);
if (!isnull) {
ArrayType* a = DatumGetArrayTypeP(tmp);
int i;
Assert(ARR_ELEMTYPE(a) == TEXTOID);
Assert(ARR_NDIM(a) == 1);
Assert(ARR_LBOUND(a)[0] == 1);
for (i = 1; i <= ARR_DIMS(a)[0]; i++) {
Datum d;
d = array_ref(a,
1,
&i,
-1 ,
-1 ,
false ,
'i' ,
&isnull);
if (!isnull) {
char* configitem = TextDatumGetCString(d);
char* pos = NULL;
pos = strchr(configitem, '=');
if (pos == NULL)
continue;
*pos++ = '\0';
appendStringInfo(&buf, " SET %s TO ", quote_identifier(configitem));
* Some GUC variable names are 'LIST' type and hence must not
* be quoted.
*/
if (pg_strcasecmp(configitem, "DateStyle") == 0 || pg_strcasecmp(configitem, "search_path") == 0)
appendStringInfoString(&buf, pos);
else
simple_quote_literal(&buf, pos);
appendStringInfoChar(&buf, '\n');
}
}
}
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
if (!isnull) {
simple_quote_literal(&buf, TextDatumGetCString(tmp));
appendStringInfoString(&buf, ", ");
}
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosrc, &isnull);
if (isnull)
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null prosrc")));
prosrc = TextDatumGetCString(tmp);
* We always use dollar quoting. Figure out a suitable delimiter.
*
* Since the user is likely to be editing the function body string, we
* shouldn't use a short delimiter that he might easily create a conflict
* with. Hence prefer "$function$", but extend if needed.
*/
initStringInfo(&dq);
if ((!proIsProcedure) && (!isOraFunc)) {
appendStringInfoString(&dq, "$function");
while (strstr(prosrc, dq.data) != NULL)
appendStringInfoChar(&dq, 'x');
appendStringInfoChar(&dq, '$');
}
appendStringInfoString(&buf, dq.data);
for (p = buf.data; *p; p++)
if (*p == '\n')
(*headerlines)++;
appendStringInfoString(&buf, prosrc);
appendStringInfoString(&buf, dq.data);
if (proIsProcedure || isOraFunc) {
appendStringInfoString(&buf, ";\n/");
} else {
appendStringInfoString(&buf, ";");
}
appendStringInfoString(&buf, "\n");
ReleaseSysCache(langtup);
ReleaseSysCache(proctup);
pfree_ext(dq.data);
return buf.data;
}
* Guts of pg_get_function_result: append the function's return type
* to the specified buffer.
*/
static void print_function_rettype(StringInfo buf, HeapTuple proctup)
{
Form_pg_proc proc = (Form_pg_proc)GETSTRUCT(proctup);
int ntabargs = 0;
StringInfoData rbuf;
bool is_client_logic = false;
initStringInfo(&rbuf);
if (proc->proretset) {
appendStringInfoString(&rbuf, "TABLE(");
ntabargs = print_function_arguments(&rbuf, proctup, true, false);
if (ntabargs > 0)
appendStringInfoString(&rbuf, ")");
else
resetStringInfo(&rbuf);
}
Oid ret_type_id = proc->prorettype;
if (ntabargs == 0) {
if (proc->proretset)
appendStringInfoString(&rbuf, "SETOF ");
if (IsClientLogicType(proc->prorettype)) {
HeapTuple gs_oldtup = SearchSysCache1(GSCLPROCID, ObjectIdGetDatum(HeapTupleGetOid(proctup)));
if (HeapTupleIsValid(gs_oldtup)) {
bool isnull = false;
Datum rettype_orig =
SysCacheGetAttr(GSCLPROCID, gs_oldtup, Anum_gs_encrypted_proc_prorettype_orig, &isnull);
if (!isnull) {
ret_type_id = DatumGetObjectId(rettype_orig);
is_client_logic = true;
}
ReleaseSysCache(gs_oldtup);
}
}
appendStringInfoString(&rbuf, format_type_be(ret_type_id));
if (is_client_logic) {
appendStringInfoString(&rbuf, " encrypted");
}
}
appendStringInfoString(buf, rbuf.data);
}
* get param string from pg_proc_proargsrc.
*/
static void print_function_ora_arguments(StringInfo buf, HeapTuple proctup)
{
Datum tmp;
bool isnull = false;
const char* proargsrc = NULL;
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargsrc, &isnull);
Assert(!isnull);
proargsrc = TextDatumGetCString(tmp);
appendStringInfoString(buf, proargsrc);
}
* Common code for pg_get_function_arguments and pg_get_function_result:
* append the desired subset of arguments to buf. We print only TABLE
* arguments when print_table_args is true, and all the others when it's false.
* We print argument defaults only if print_defaults is true.
* Function return value is the number of arguments printed.
*/
static int print_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults)
{
Form_pg_proc proc = (Form_pg_proc)GETSTRUCT(proctup);
int numargs;
Oid* argtypes = NULL;
char** argnames = NULL;
char* argmodes = NULL;
int insertorderbyat = -1;
int argsprinted;
int inputargno;
ListCell* nextargdefault = NULL;
int i;
Datum defposdatum;
int2vector* defpos = NULL;
int counter = 0;
bool *is_client_logic = NULL;
numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes);
if (numargs) {
is_client_logic = (bool*)palloc0(numargs * sizeof(bool));
}
replace_cl_types_in_argtypes(HeapTupleGetOid(proctup), numargs, argtypes, is_client_logic);
if (print_defaults && proc->pronargdefaults > 0) {
Datum proargdefaults;
bool isnull = false;
char* str = NULL;
List* argdefaults = NIL;
proargdefaults = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargdefaults, &isnull);
Assert(!isnull);
str = TextDatumGetCString(proargdefaults);
argdefaults = (List*)stringToNode(str);
Assert(IsA(argdefaults, List));
pfree_ext(str);
nextargdefault = list_head(argdefaults);
if (proc->pronargs <= FUNC_MAX_ARGS_INROW) {
defposdatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prodefaultargpos, &isnull);
Assert(!isnull);
defpos = (int2vector*)DatumGetPointer(defposdatum);
} else {
defposdatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prodefaultargposext, &isnull);
Assert(!isnull);
defpos = (int2vector*)PG_DETOAST_DATUM(defposdatum);
}
}
if (proc->proisagg) {
Oid proctupoid;
HeapTuple aggtup;
Form_pg_aggregate aggform;
proctupoid = HeapTupleGetOid(proctup);
aggtup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(proctupoid));
if (!HeapTupleIsValid(aggtup))
ereport(ERROR, (errmodule(MOD_OPT), (errmsg("cache lookup failed for aggregate %u", proctupoid))));
aggform = (Form_pg_aggregate)GETSTRUCT(aggtup);
if (AGGKIND_IS_ORDERED_SET(aggform->aggkind))
insertorderbyat = aggform->aggnumdirectargs;
ReleaseSysCache(aggtup);
}
argsprinted = 0;
inputargno = 0;
for (i = 0; i < numargs; i++) {
Oid argtype = argtypes[i];
char* argname = argnames ? argnames[i] : NULL;
char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
const char* modename = NULL;
bool isinput = false;
switch (argmode) {
case PROARGMODE_IN:
modename = "";
isinput = true;
break;
case PROARGMODE_INOUT:
modename = "INOUT ";
isinput = true;
break;
case PROARGMODE_OUT:
modename = "OUT ";
isinput = false;
break;
case PROARGMODE_VARIADIC:
modename = "VARIADIC ";
isinput = true;
break;
case PROARGMODE_TABLE:
modename = "";
isinput = false;
break;
default:
ereport(
ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid parameter mode '%c'", argmode)));
modename = NULL;
isinput = false;
break;
}
if (isinput)
inputargno++;
if (print_table_args != (argmode == PROARGMODE_TABLE))
continue;
if (argsprinted == insertorderbyat) {
if (argsprinted)
appendStringInfoChar(buf, ' ');
appendStringInfoString(buf, "ORDER BY ");
} else if (argsprinted)
appendStringInfoString(buf, ", ");
appendStringInfoString(buf, modename);
if ((argname != NULL) && argname[0])
appendStringInfo(buf, "%s ", quote_identifier(argname));
appendStringInfoString(buf, format_type_be(argtype));
if (is_client_logic[i]) {
appendStringInfoString(buf, " encrypted");
}
* fetch default value for some argument
*/
if (print_defaults && isinput && (defpos != NULL) &&
counter < defpos->dim1
&& (argsprinted == defpos->values[counter])) {
Node* expr = NULL;
Assert(nextargdefault != NULL);
expr = (Node*)lfirst(nextargdefault);
nextargdefault = lnext(nextargdefault);
appendStringInfo(buf, " DEFAULT %s", deparse_expression(expr, NIL, false, false));
counter++;
}
argsprinted++;
if (argsprinted == insertorderbyat && i == numargs - 1) {
i--;
print_defaults = false;
}
}
pfree_ext(is_client_logic);
return argsprinted;
}
static void print_parallel_enable(StringInfo buf, HeapTuple procTup, int2 parallelCursorSeq, Oid funcid)
{
bool isNull = false;
Datum proargnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isNull);
Datum* elems = NULL;
int nelems;
Assert(!isNull);
deconstruct_array(DatumGetArrayTypeP(proargnames), TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
appendStringInfo(buf, " PARALLEL_ENABLE (PARTITION %s BY ", TextDatumGetCString(elems[parallelCursorSeq]));
FunctionPartitionStrategy strategy;
List* partkey = NIL;
strategy = GetParallelStrategyAndKey(funcid, &partkey);
if (strategy == FUNC_PARTITION_ANY) {
appendStringInfoString(buf, "ANY)");
} else if (strategy == FUNC_PARTITION_HASH) {
appendStringInfoString(buf, "HASH(");
ListCell* lc = NULL;
foreach (lc, partkey) {
char* keyName = (char*)lfirst(lc);
if (lnext(lc) != NULL) {
appendStringInfo(buf, "%s,", keyName);
} else {
appendStringInfo(buf, "%s))", keyName);
}
}
}
}
* deparse_expression - General utility for deparsing expressions
*
* calls deparse_expression_pretty with all prettyPrinting disabled
*/
char* deparse_expression(Node* expr, List* dpcontext, bool forceprefix, bool showimplicit, bool no_alias)
{
return deparse_expression_pretty(expr, dpcontext, forceprefix, showimplicit, 0, 0, no_alias);
}
* deparse_expression_pretty - General utility for deparsing expressions
*
* expr is the node tree to be deparsed. It must be a transformed expression
* tree (ie, not the raw output of gram.y).
*
* dpcontext is a list of deparse_namespace nodes representing the context
* for interpreting Vars in the node tree.
*
* forceprefix is TRUE to force all Vars to be prefixed with their table names.
*
* showimplicit is TRUE to force all implicit casts to be shown explicitly.
*
* tries to pretty up the output according to prettyFlags and startIndent.
*
* The result is a palloc'd string.
* ----------
*/
static char* deparse_expression_pretty(
Node* expr, List* dpcontext, bool forceprefix, bool showimplicit, int prettyFlags, int startIndent, bool no_alias)
{
StringInfoData buf;
deparse_context context;
initStringInfo(&buf);
context.buf = &buf;
context.namespaces = dpcontext;
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = forceprefix;
context.prettyFlags = prettyFlags;
#ifdef PGXC
context.finalise_aggs = false;
context.sortgroup_colno = false;
context.parser_arg = NULL;
context.viewdef = false;
context.is_fqs = false;
#endif
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = startIndent;
context.qrw_phase = false;
context.is_upsert_clause = false;
get_rule_expr(expr, &context, showimplicit, no_alias);
return buf.data;
}
* deparse_context_for - Build deparse context for a single relation
*
* Given the reference name (alias) and OID of a relation, build deparsing
* context for an expression referencing only that relation (as varno 1,
* varlevelsup 0). This is sufficient for many uses of deparse_expression.
* ----------
*/
List* deparse_context_for(const char* aliasname, Oid relid)
{
deparse_namespace* dpns = NULL;
RangeTblEntry* rte = NULL;
dpns = (deparse_namespace*)palloc0(sizeof(deparse_namespace));
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = relid;
rte->relkind = RELKIND_RELATION;
rte->eref = makeAlias(aliasname, NIL);
rte->lateral = false;
rte->inh = false;
rte->inFromCl = true;
dpns->rtable = list_make1(rte);
dpns->ctes = NIL;
#ifdef PGXC
dpns->remotequery = false;
#endif
return list_make1(dpns);
}
* deparse_context_for_planstate - Build deparse context for a plan
*
* When deparsing an expression in a Plan tree, we might have to resolve
* OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
* provide the parent PlanState node. Then OUTER_VAR and INNER_VAR references
* can be resolved by drilling down into the left and right child plans.
* Similarly, INDEX_VAR references can be resolved by reference to the
* indextlist given in the parent IndexOnlyScan node. (Note that we don't
* currently support deparsing of indexquals in regular IndexScan or
* BitmapIndexScan nodes; for those, we can only deparse the indexqualorig
* fields, which won't contain INDEX_VAR Vars.)
*
* Note: planstate really ought to be declared as "PlanState *", but we use
* "Node *" to avoid having to include execnodes.h in builtins.h.
*
* The ancestors list is a list of the PlanState's parent PlanStates, the
* most-closely-nested first. This is needed to resolve PARAM_EXEC Params.
* Note we assume that all the PlanStates share the same rtable.
*
* The plan's rangetable list must also be passed. We actually prefer to use
* the rangetable to resolve simple Vars, but the plan inputs are necessary
* for Vars with special varnos.
*/
List* deparse_context_for_planstate(Node* planstate, List* ancestors, List* rtable)
{
deparse_namespace* dpns = NULL;
dpns = (deparse_namespace*)palloc0(sizeof(deparse_namespace));
dpns->rtable = rtable;
dpns->ctes = NIL;
#ifdef PGXC
dpns->remotequery = false;
#endif
set_deparse_planstate(dpns, (PlanState*)planstate);
dpns->ancestors = ancestors;
return list_make1(dpns);
}
* set_deparse_planstate: set up deparse_namespace to parse subexpressions
* of a given PlanState node
*
* This sets the planstate, outer_planstate, inner_planstate, outer_tlist,
* inner_tlist, and index_tlist fields. Caller is responsible for adjusting
* the ancestors list if necessary. Note that the rtable and ctes fields do
* not need to change when shifting attention to different plan nodes in a
* single plan tree.
*/
static void set_deparse_planstate(deparse_namespace* dpns, PlanState* ps)
{
dpns->planstate = ps;
* We special-case Append and MergeAppend to pretend that the first child
* plan is the OUTER referent; we have to interpret OUTER Vars in their
* tlists according to one of the children, and the first one is the most
* natural choice. Likewise special-case ModifyTable to pretend that the
* first child plan is the OUTER referent; this is to support RETURNING
* lists containing references to non-target relations.
*/
if (IsA(ps, AppendState))
dpns->outer_planstate = ((AppendState*)ps)->appendplans[0];
#ifdef USE_SPQ
else if (IsA(ps, SequenceState))
dpns->outer_planstate = ((SequenceState *) ps)->subplans[1];
#endif
else if (IsA(ps, VecAppendState))
dpns->outer_planstate = ((VecAppendState*)ps)->appendplans[0];
else if (IsA(ps, MergeAppendState))
dpns->outer_planstate = ((MergeAppendState*)ps)->mergeplans[0];
else if (IsA(ps, ModifyTableState))
dpns->outer_planstate = ((ModifyTableState*)ps)->mt_plans[0];
else if (IsA(ps, VecModifyTableState))
dpns->outer_planstate = ((VecModifyTableState*)ps)->mt_plans[0];
else
dpns->outer_planstate = outerPlanState(ps);
if (dpns->outer_planstate != NULL)
dpns->outer_tlist = dpns->outer_planstate->plan->targetlist;
else
dpns->outer_tlist = NIL;
* For a SubqueryScan, pretend the subplan is INNER referent. (We don't
* use OUTER because that could someday conflict with the normal meaning.)
* Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
* For DUPLICATE KEY UPDATE we just need the inner tlist to point to the
* excluded expression's tlist. (Similar to the SubqueryScan we don't want
* to reuse OUTER, it's used for RETURNING in some modify table cases,
* although not INSERT ... ON DUPLICATE KEY UPDATE).
*/
if (IsA(ps, SubqueryScanState))
dpns->inner_planstate = ((SubqueryScanState*)ps)->subplan;
#ifdef USE_SPQ
else if (IsA(ps, SequenceState))
dpns->inner_planstate = ((SequenceState *) ps)->subplans[0];
#endif
else if (IsA(ps, VecSubqueryScanState))
dpns->inner_planstate = ((VecSubqueryScanState*)ps)->subplan;
else if (IsA(ps, CteScanState))
dpns->inner_planstate = ((CteScanState*)ps)->cteplanstate;
else if (IsA(ps, ModifyTableState) || IsA(ps, VecModifyTableState) || IsA(ps, DistInsertSelectState)) {
ModifyTableState* mps = (ModifyTableState*)ps;
dpns->outer_planstate = mps->mt_plans[0];
dpns->inner_planstate = ps;
* For merge into, we should deparse the inner plan, since the targetlist and qual will
* reference sourceTargetList, which comes from outer plan of the join (source table)
*/
if (mps->operation == CMD_MERGE) {
PlanState* jplanstate = dpns->outer_planstate;
if (IsA(jplanstate, StreamState) || IsA(jplanstate, VecStreamState))
jplanstate = jplanstate->lefttree;
if (jplanstate->plan != NULL && IsJoinPlan((Node*)jplanstate->plan) &&
((Join*)jplanstate->plan)->jointype == JOIN_RIGHT) {
dpns->inner_planstate = innerPlanState(jplanstate);
} else {
dpns->inner_planstate = outerPlanState(jplanstate);
}
}
} else
dpns->inner_planstate = innerPlanState(ps);
if (IsA(ps, ModifyTableState) && ((ModifyTableState*)ps)->mt_upsert->us_excludedtlist != NIL) {
* mark upsert clause under PlanState.
*/
dpns->inner_tlist = ((ModifyTableState*)ps)->mt_upsert->us_excludedtlist;
} else if ((IsA(ps, ModifyTableState) || IsA(ps, VecModifyTableState) || IsA(ps, DistInsertSelectState)) &&
((ModifyTableState *)ps)->operation == CMD_MERGE) {
dpns->inner_tlist = ((ModifyTable*)(ps->plan))->mergeSourceTargetList;
} else if (dpns->inner_planstate != NULL) {
dpns->inner_tlist = dpns->inner_planstate->plan->targetlist;
} else {
dpns->inner_tlist = NIL;
}
if (IsA(ps->plan, IndexOnlyScan))
dpns->index_tlist = ((IndexOnlyScan*)ps->plan)->indextlist;
#ifdef USE_SPQ
else if IsA(ps->plan, SpqIndexOnlyScan)
dpns->index_tlist = ((IndexOnlyScan*)ps->plan)->indextlist;
#endif
else if (IsA(ps->plan, ForeignScan))
dpns->index_tlist = ((ForeignScan *)ps->plan)->fdw_scan_tlist;
else if (IsA(ps->plan, ExtensiblePlan))
dpns->index_tlist = ((ExtensiblePlan*)ps->plan)->extensible_plan_tlist;
else
dpns->index_tlist = NIL;
}
#ifdef PGXC
* This is a special case deparse context to be used at the planning time to
* generate query strings and expressions for remote shipping.
*
* XXX We should be careful while using this since the support is quite
* limited. The only supported use case at this point is for remote join
* reduction and some simple plan trees rooted by Agg node having a single
* RemoteQuery node as leftree.
*/
List* deparse_context_for_plan(Node* plan, List* ancestors, List* rtable)
{
deparse_namespace* dpns = NULL;
dpns = (deparse_namespace*)palloc0(sizeof(deparse_namespace));
dpns->rtable = rtable;
dpns->ctes = NIL;
dpns->remotequery = false;
set_deparse_plan(dpns, (Plan*)plan);
dpns->ancestors = ancestors;
return list_make1(dpns);
}
* Set deparse context for Plan. Only those plan nodes which are immediate (or
* through simple nodes) parents of RemoteQuery nodes are supported right now.
*
* This is a kind of work-around since the new deparse interface (since 9.1)
* expects a PlanState node. But planstates are instantiated only at execution
* time when InitPlan is called. But we are required to deparse the query
* during planning time, so we hand-cook these dummy PlanState nodes instead of
* init-ing the plan. Another approach could have been to delay the query
* generation to the execution time, but we are not yet sure if this can be
* safely done, especially for remote join reduction.
*/
static void set_deparse_plan(deparse_namespace* dpns, Plan* plan)
{
if (IsA(plan, NestLoop)) {
NestLoop* nestloop = (NestLoop*)plan;
dpns->planstate = (PlanState*)makeNode(NestLoopState);
dpns->planstate->plan = plan;
dpns->outer_planstate = (PlanState*)makeNode(PlanState);
dpns->outer_planstate->plan = nestloop->join.plan.lefttree;
dpns->inner_planstate = (PlanState*)makeNode(PlanState);
dpns->inner_planstate->plan = nestloop->join.plan.righttree;
} else if (IsA(plan, RemoteQuery)) {
dpns->planstate = (PlanState*)makeNode(PlanState);
dpns->planstate->plan = plan;
} else if (IsA(plan, Agg) || IsA(plan, Group)) {
* We expect plan tree as Group/Agg->Sort->Result->Material->RemoteQuery,
* Result, Material nodes are optional. Sort is compulsory for Group but not
* for Agg.
* anything else is not handled right now.
*/
Plan* temp_plan = plan->lefttree;
Plan* remote_scan = NULL;
if (temp_plan && IsA(temp_plan, Sort))
temp_plan = temp_plan->lefttree;
if (temp_plan && IsA(temp_plan, BaseResult))
temp_plan = temp_plan->lefttree;
if (temp_plan && IsA(temp_plan, Material))
temp_plan = temp_plan->lefttree;
if (temp_plan && IsA(temp_plan, RemoteQuery))
remote_scan = temp_plan;
if (remote_scan == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Deparse of this query at planning is not supported yet")));
dpns->planstate = (PlanState*)makeNode(PlanState);
dpns->planstate->plan = plan;
} else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Deparse of this query at planning not supported yet")));
}
#endif
* push_child_plan: temporarily transfer deparsing attention to a child plan
*
* When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the
* deparse context in case the referenced expression itself uses
* OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid
* affecting levelsup issues (although in a Plan tree there really shouldn't
* be any).
*
* Caller must provide a local deparse_namespace variable to save the
* previous state for pop_child_plan.
*/
static void push_child_plan(deparse_namespace* dpns, PlanState* ps, deparse_namespace* save_dpns)
{
*save_dpns = *dpns;
* Currently we don't bother to adjust the ancestors list, because an
* OUTER_VAR or INNER_VAR reference really shouldn't contain any Params
* that would be set by the parent node itself. If we did want to adjust
* the list, lcons'ing dpns->planstate onto dpns->ancestors would be the
* appropriate thing --- and pop_child_plan would need to undo the change
* to the list.
*/
set_deparse_planstate(dpns, ps);
}
* pop_child_plan: undo the effects of push_child_plan
*/
static void pop_child_plan(deparse_namespace* dpns, deparse_namespace* save_dpns)
{
*dpns = *save_dpns;
}
* push_ancestor_plan: temporarily transfer deparsing attention to an
* ancestor plan
*
* When expanding a Param reference, we must adjust the deparse context
* to match the plan node that contains the expression being printed;
* otherwise we'd fail if that expression itself contains a Param or
* OUTER_VAR/INNER_VAR/INDEX_VAR variable.
*
* The target ancestor is conveniently identified by the ListCell holding it
* in dpns->ancestors.
*
* Caller must provide a local deparse_namespace variable to save the
* previous state for pop_ancestor_plan.
*/
static void push_ancestor_plan(deparse_namespace* dpns, ListCell* ancestor_cell, deparse_namespace* save_dpns)
{
PlanState* ps = (PlanState*)lfirst(ancestor_cell);
List* ancestors = NIL;
*save_dpns = *dpns;
ancestors = NIL;
while ((ancestor_cell = lnext(ancestor_cell)) != NULL)
ancestors = lappend(ancestors, lfirst(ancestor_cell));
dpns->ancestors = ancestors;
set_deparse_planstate(dpns, ps);
}
* pop_ancestor_plan: undo the effects of push_ancestor_plan
*/
static void pop_ancestor_plan(deparse_namespace* dpns, deparse_namespace* save_dpns)
{
list_free_ext(dpns->ancestors);
*dpns = *save_dpns;
}
* make_ruledef - reconstruct the CREATE RULE command
* for a given pg_rewrite tuple
* ----------
*/
static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags)
{
char* rulename = NULL;
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead = false;
char* ev_qual = NULL;
char* ev_action = NULL;
List* actions = NIL;
Relation ev_relation = NULL;
TupleDesc viewResultDesc = NULL;
int fno;
Datum dat;
bool isnull = false;
* Get the attribute values from the rules tuple
*/
fno = SPI_fnumber(rulettc, "rulename");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
rulename = NameStr(*(DatumGetName(dat)));
fno = SPI_fnumber(rulettc, "ev_type");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
ev_type = DatumGetChar(dat);
fno = SPI_fnumber(rulettc, "ev_class");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
ev_class = DatumGetObjectId(dat);
fno = SPI_fnumber(rulettc, "ev_attr");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
ev_attr = DatumGetInt16(dat);
fno = SPI_fnumber(rulettc, "is_instead");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
is_instead = DatumGetBool(dat);
fno = SPI_fnumber(rulettc, "ev_qual");
ev_qual = SPI_getvalue(ruletup, rulettc, fno);
fno = SPI_fnumber(rulettc, "ev_action");
ev_action = SPI_getvalue(ruletup, rulettc, fno);
if (ev_action != NULL)
actions = (List*)stringToNode(ev_action);
ev_relation = heap_open(ev_class, AccessShareLock);
* Build the rules definition text
*/
appendStringInfo(buf, "CREATE RULE %s AS", quote_identifier(rulename));
if ((unsigned int)prettyFlags & PRETTYFLAG_INDENT)
appendStringInfoString(buf, "\n ON ");
else
appendStringInfoString(buf, " ON ");
switch (ev_type) {
case '1':
appendStringInfo(buf, "SELECT");
viewResultDesc = RelationGetDescr(ev_relation);
break;
case '2':
appendStringInfo(buf, "UPDATE");
break;
case '3':
appendStringInfo(buf, "INSERT");
break;
case '4':
appendStringInfo(buf, "DELETE");
break;
case '6':
{
Query* query = (Query*)linitial(actions);
if (IsA(query->utilityStmt, CopyStmt)) {
appendStringInfo(buf, "COPY");
} else if (IsA(query->utilityStmt, AlterTableStmt)) {
appendStringInfo(buf, "ALTER");
}
break;
}
default:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rule \"%s\" has unsupported event type %d", rulename, ev_type)));
break;
}
appendStringInfo(buf, " TO %s", generate_relation_name(ev_class, NIL));
if (ev_attr > 0)
appendStringInfo(buf, ".%s", quote_identifier(get_relid_attribute_name(ev_class, ev_attr)));
if (ev_qual == NULL)
ev_qual = "";
if (strlen(ev_qual) > 0 && strcmp(ev_qual, "<>") != 0) {
Node* qual = NULL;
Query* query = NULL;
deparse_context context;
deparse_namespace dpns;
if (prettyFlags & PRETTYFLAG_INDENT)
appendStringInfoString(buf, "\n ");
appendStringInfo(buf, " WHERE ");
qual = (Node*)stringToNode(ev_qual);
* We need to make a context for recognizing any Vars in the qual
* (which can only be references to OLD and NEW). Use the rtable of
* the first query in the action list for this purpose.
*/
query = (Query*)linitial(actions);
* If the action is INSERT...SELECT, OLD/NEW have been pushed down
* into the SELECT, and that's what we need to look at. (Ugly kluge
* ... try to fix this when we redesign querytrees.)
*/
query = getInsertSelectQuery(query, NULL);
AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = list_make1(&dpns);
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = (list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
context.qrw_phase = false;
#ifdef PGXC
context.finalise_aggs = false;
context.sortgroup_colno = false;
context.parser_arg = NULL;
context.viewdef = false;
context.is_fqs = false;
#endif
context.is_upsert_clause = false;
errno_t rc = memset_s(&dpns, sizeof(dpns), 0, sizeof(dpns));
securec_check(rc, "\0", "\0");
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
#ifdef PGXC
dpns.remotequery = false;
#endif
get_rule_expr(qual, &context, false);
}
appendStringInfo(buf, " DO ");
if (is_instead)
appendStringInfo(buf, "INSTEAD ");
if (list_length(actions) > 1) {
ListCell* action = NULL;
Query* query = NULL;
appendStringInfo(buf, "(");
foreach (action, actions) {
query = (Query*)lfirst(action);
get_query_def(query,
buf,
NIL,
viewResultDesc,
prettyFlags,
WRAP_COLUMN_DEFAULT,
0
#ifdef PGXC
,
false,
false,
NULL
#endif
,
false,
false,
false,
!GetPgObjectValid(ev_class, RelationGetRelkind(ev_relation)));
if (prettyFlags)
appendStringInfo(buf, ";\n");
else
appendStringInfo(buf, "; ");
}
appendStringInfo(buf, ");");
} else if (list_length(actions) == 0) {
appendStringInfo(buf, "NOTHING;");
} else {
Query* query = NULL;
query = (Query*)linitial(actions);
get_query_def(query,
buf,
NIL,
viewResultDesc,
prettyFlags,
WRAP_COLUMN_DEFAULT,
0
#ifdef PGXC
,
false,
false,
NULL
#endif
,
false,
false,
false,
!GetPgObjectValid(ev_class, RelationGetRelkind(ev_relation)));
appendStringInfo(buf, ";");
}
heap_close(ev_relation, AccessShareLock);
}
* make_viewdef - reconstruct the SELECT part of a
* view rewrite rule
* ----------
*/
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags, int wrapColumn)
{
Query* query = NULL;
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead = false;
char* ev_qual = NULL;
char* ev_action = NULL;
List* actions = NIL;
Relation ev_relation;
int fno;
bool isnull = false;
* Get the attribute values from the rules tuple
*/
fno = SPI_fnumber(rulettc, "ev_type");
ev_type = (char)SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_class");
ev_class = (Oid)SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_attr");
ev_attr = (int2)SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "is_instead");
is_instead = (bool)SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_qual");
ev_qual = SPI_getvalue(ruletup, rulettc, fno);
fno = SPI_fnumber(rulettc, "ev_action");
ev_action = SPI_getvalue(ruletup, rulettc, fno);
if (ev_action != NULL)
actions = (List*)stringToNode(ev_action);
if (list_length(actions) != 1) {
appendStringInfo(buf, "Not a view");
return;
}
query = (Query*)linitial(actions);
if (ev_type != '1' || ev_attr >= 0 || !is_instead || strcmp(ev_qual, "<>") != 0 ||
query->commandType != CMD_SELECT) {
appendStringInfo(buf, "Not a view");
return;
}
ev_relation = heap_open(ev_class, AccessShareLock);
char relKind = get_rel_relkind(ev_class);
if (ev_class >= FirstNormalObjectId && !GetPgObjectValid(ev_class, relKind)) {
if (!ValidateDependView(ev_class, relKind, buf)) {
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("View %s references invalid table(s), view(s) or column(s).", get_rel_name(ev_class))));
} else {
heap_close(ev_relation, AccessShareLock);
return;
}
get_query_def(query,
buf,
NIL,
RelationGetDescr(ev_relation),
prettyFlags,
wrapColumn,
0
#ifdef PGXC
,
false,
false,
NULL
#endif
,
false,
true,
false,
true);
} else {
get_query_def(query,
buf,
NIL,
RelationGetDescr(ev_relation),
prettyFlags,
wrapColumn,
0
#ifdef PGXC
,
false,
false,
NULL
#endif
,
false,
true);
}
appendStringInfo(buf, ";");
heap_close(ev_relation, AccessShareLock);
}
#ifdef PGXC
* deparse_query - Parse back one query parsetree
*
* Purpose of this function is to build up statement for a RemoteQuery
* The query generated has all object names schema-qualified. This is
* done by temporarily setting search_path to NIL.
* It calls get_query_def without pretty print flags.
*
* Caution: get_query_def calls AcquireRewriteLocks, which might modify the RTEs
* in place. So it is generally appropriate for the caller of this routine to
* have first done a copyObject() to make a writable copy of the querytree in
* the current memory context.
* ----------
*/
void deparse_query(Query* query, StringInfo buf, List* parentnamespace, bool finalise_aggs, bool sortgroup_colno,
void* parserArg, bool qrw_phase, bool is_fqs)
{
if (u_sess->hook_cxt.deparseQueryHook != NULL) {
((deparse_query_func)(u_sess->hook_cxt.deparseQueryHook))(query, buf, parentnamespace,
finalise_aggs, sortgroup_colno, parserArg, qrw_phase, is_fqs);
return;
}
OverrideSearchPath* tmp_search_path = NULL;
List* schema_list = NIL;
ListCell* schema = NULL;
* Before deparsing the query, set the search_patch to NIL so that all the
* object names in the deparsed query are schema qualified. This is required
* so as to not have any dependency on current search_path. For e.g the same
* remote query can be used even if search_path changes between two executes
* of a prepared statement.
* Note: We do not apply the above solution for temp tables for reasons shown
* in the below comment.
*/
tmp_search_path = GetOverrideSearchPath(CurrentMemoryContext);
tmp_search_path->addTemp = true;
schema_list = tmp_search_path->schemas;
foreach (schema, schema_list) {
if (isTempNamespace(lfirst_oid(schema))) {
* should be qualified, otherwise the object name would possibly
* be resolved from some other schema. We force schema
* qualification by making sure the overridden search_path does not
* have pg_temp implicitly added.
* Why do we not *always* let the pg_temp qualification be there ?
* Because the pg_temp schema name always gets deparsed into the
* actual temp schema names like pg_temp_[1-9]*, and not the dummy
* name pg_temp. And we do not want to use these names because
* they are specific to the local node. pg_temp_2.obj1 at node 1
* may be present in pg_temp_3 at node2.
*/
if (list_head(schema_list) != schema)
tmp_search_path->addTemp = false;
break;
}
}
tmp_search_path->schemas = NIL;
PushOverrideSearchPath(tmp_search_path);
get_query_def(query,
buf,
parentnamespace,
NULL,
PRETTYFLAG_PAREN,
WRAP_COLUMN_DEFAULT,
0
#ifdef PGXC
,
finalise_aggs,
sortgroup_colno,
parserArg
#endif
,
qrw_phase,
false,
is_fqs);
PopOverrideSearchPath();
}
#endif
* get_query_def - Parse back one query parsetree
*
* If resultDesc is not NULL, then it is the output tuple descriptor for
* the view represented by a SELECT query.
* ----------
*/
void get_query_def(Query* query, StringInfo buf, List* parentnamespace, TupleDesc resultDesc, int prettyFlags,
int wrapColumn, int startIndent
#ifdef PGXC
,
bool finalise_aggs, bool sortgroup_colno, void* parserArg
#endif
,
bool qrw_phase, bool viewdef, bool is_fqs, bool skip_lock)
{
deparse_context context;
deparse_namespace dpns;
CHECK_FOR_INTERRUPTS();
check_stack_depth();
* Before we begin to examine the query, acquire locks on referenced
* relations, and fix up deleted columns in JOIN RTEs. This ensures
* consistent results. Note we assume it's OK to scribble on the passed
* querytree!For qrw phase, formal rewrite already add lock on relations,
* and we don't want to change query tree again, so skip this.
*/
if (!qrw_phase && !skip_lock)
AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = (parentnamespace != NIL || list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
context.wrapColumn = wrapColumn;
context.indentLevel = startIndent;
#ifdef PGXC
context.finalise_aggs = finalise_aggs;
context.sortgroup_colno = sortgroup_colno;
context.parser_arg = parserArg;
#endif
context.qrw_phase = qrw_phase;
context.viewdef = viewdef;
context.is_fqs = is_fqs;
context.is_upsert_clause = false;
context.skip_lock = skip_lock;
errno_t rc = memset_s(&dpns, sizeof(dpns), 0, sizeof(dpns));
securec_check(rc, "", "");
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
#ifdef PGXC
dpns.remotequery = false;
#endif
switch (query->commandType) {
case CMD_SELECT:
get_select_query_def(query, &context, resultDesc);
break;
case CMD_UPDATE:
get_update_query_def(query, &context);
break;
case CMD_INSERT:
get_insert_query_def(query, &context);
break;
case CMD_DELETE:
get_delete_query_def(query, &context);
break;
case CMD_MERGE:
appendStringInfo(buf, "MERGE");
break;
case CMD_NOTHING:
appendStringInfo(buf, "NOTHING");
break;
case CMD_UTILITY:
get_utility_query_def(query, &context);
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized query command type: %d", query->commandType)));
break;
}
}
* get_values_def - Parse back a VALUES list
* ----------
*/
static void get_values_def(List* values_lists, deparse_context* context)
{
StringInfo buf = context->buf;
bool first_list = true;
ListCell* vtl = NULL;
appendStringInfoString(buf, "VALUES ");
foreach (vtl, values_lists) {
List* sublist = (List*)lfirst(vtl);
bool first_col = true;
ListCell* lc = NULL;
if (first_list)
first_list = false;
else
appendStringInfoString(buf, ", ");
appendStringInfoChar(buf, '(');
foreach (lc, sublist) {
Node* col = (Node*)lfirst(lc);
if (first_col)
first_col = false;
else
appendStringInfoChar(buf, ',');
* Strip any top-level nodes representing indirection assignments,
* then print the result.
*/
get_rule_expr(processIndirection(col, context, false), context, false);
}
appendStringInfoChar(buf, ')');
}
}
* get_with_clause - Parse back a WITH clause
* ----------
*/
static void get_with_clause(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
const char* sep = NULL;
ListCell* l = NULL;
if (query->cteList == NIL)
return;
if (PRETTY_INDENT(context)) {
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoChar(buf, ' ');
}
if (query->hasRecursive)
sep = "WITH RECURSIVE ";
else
sep = "WITH ";
foreach (l, query->cteList) {
CommonTableExpr* cte = (CommonTableExpr*)lfirst(l);
appendStringInfoString(buf, sep);
appendStringInfoString(buf, quote_identifier(cte->ctename));
if (cte->aliascolnames) {
bool first = true;
ListCell* col = NULL;
appendStringInfoChar(buf, '(');
foreach (col, cte->aliascolnames) {
if (first)
first = false;
else
appendStringInfoString(buf, ", ");
appendStringInfoString(buf, quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
appendStringInfoString(buf, " AS ");
switch (cte->ctematerialized)
{
case CTEMaterializeDefault:
break;
case CTEMaterializeAlways:
appendStringInfoString(buf, "MATERIALIZED ");
break;
case CTEMaterializeNever:
appendStringInfoString(buf, "NOT MATERIALIZED ");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized materialization option: %d", (int)cte->ctematerialized)));
}
appendStringInfoChar(buf, '(');
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
get_query_def((Query*)cte->ctequery,
buf,
context->namespaces,
NULL,
context->prettyFlags,
context->wrapColumn,
context->indentLevel
#ifdef PGXC
,
context->finalise_aggs,
context->sortgroup_colno,
context->parser_arg
#endif
,
context->qrw_phase,
context->viewdef,
context->is_fqs,
context->skip_lock);
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
appendStringInfoChar(buf, ')');
sep = ", ";
}
if (PRETTY_INDENT(context)) {
context->indentLevel -= PRETTYINDENT_STD;
appendContextKeyword(context, "", 0, 0, 0);
} else
appendStringInfoChar(buf, ' ');
}
* get_select_query_def - Parse back a SELECT parsetree
* ----------
*/
static void get_select_query_def(Query* query, deparse_context* context, TupleDesc resultDesc)
{
StringInfo buf = context->buf;
List* save_windowclause = NIL;
List* save_windowtlist = NIL;
bool force_colno = false;
ListCell* l = NULL;
get_with_clause(query, context);
save_windowclause = context->windowClause;
context->windowClause = query->windowClause;
save_windowtlist = context->windowTList;
context->windowTList = query->targetList;
* If the Query node has a setOperations tree, then it's the top level of
* a UNION/INTERSECT/EXCEPT query; only the WITH, ORDER BY and LIMIT
* fields are interesting in the top query itself.
*/
if (query->setOperations) {
get_setop_query(query->setOperations, query, context, resultDesc);
force_colno = true;
} else {
get_basic_select_query(query, context, resultDesc);
force_colno = false;
}
if (query->sortClause != NIL) {
appendContextKeyword(context, " ORDER BY ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_orderby(query->sortClause, query->targetList, force_colno, context);
}
bool hasOffset = false;
if (query->limitOffset != NULL &&
!(IsA(query->limitOffset, Const) && ((Const*)(query->limitOffset))->ismaxvalue)) {
appendContextKeyword(context, " OFFSET ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
get_rule_expr(query->limitOffset, context, false);
hasOffset = true;
}
if (query->limitCount != NULL) {
if (DB_IS_CMPT(A_FORMAT) && query->isFetch) {
if (hasOffset) {
appendContextKeyword(context, " ROWS ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
}
appendContextKeyword(context, " FETCH FIRST ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
get_rule_expr(query->limitCount, context, false);
if (query->limitIsPercent) {
appendContextKeyword(context, " PERCENT ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
}
if (query->limitWithTies) {
appendContextKeyword(context, " ROWS WITH TIES", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
} else {
appendContextKeyword(context, " ROWS ONLY", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
}
} else {
appendContextKeyword(context, " LIMIT ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
if (IsA(query->limitCount, Const) && ((Const*)query->limitCount)->constisnull)
appendStringInfo(buf, "ALL");
else
get_rule_expr(query->limitCount, context, false);
}
}
if (query->hasForUpdate) {
foreach (l, query->rowMarks) {
RowMarkClause* rc = (RowMarkClause*)lfirst(l);
RangeTblEntry* rte = rt_fetch(rc->rti, query->rtable);
if (rc->pushedDown)
continue;
#ifndef ENABLE_MULTIPLE_NODES
switch (rc->strength) {
case LCS_FORKEYSHARE:
appendContextKeyword(context, " FOR KEY SHARE", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORSHARE:
appendContextKeyword(context, " FOR SHARE", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORNOKEYUPDATE:
appendContextKeyword(context, " FOR NO KEY UPDATE", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORUPDATE:
appendContextKeyword(context, " FOR UPDATE", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
default:
ereport(ERROR, (errmsg("unknown lock type: %d", rc->strength)));
break;
}
#else
appendContextKeyword(context, rc->forUpdate ? " FOR UPDATE" : " FOR SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
#endif
appendStringInfo(buf, " OF %s", quote_identifier(rte->eref->aliasname));
if (rc->waitPolicy == LockWaitError)
appendStringInfo(buf, " NOWAIT");
else if (rc->waitPolicy == LockWaitSkip)
appendStringInfo(buf, " SKIP LOCKED");
if (rc->waitSec > 0)
appendStringInfo(buf, " WAIT %d", rc->waitSec);
}
}
context->windowClause = save_windowclause;
context->windowTList = save_windowtlist;
}
* Detect whether query looks like SELECT ... FROM VALUES();
* if so, return the VALUES RTE. Otherwise return NULL.
*/
static RangeTblEntry* get_simple_values_rte(Query* query)
{
RangeTblEntry* result = NULL;
ListCell* lc = NULL;
* We want to return TRUE even if the Query also contains OLD or NEW rule
* RTEs. So the idea is to scan the rtable and see if there is only one
* inFromCl RTE that is a VALUES RTE.
*/
foreach (lc, query->rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(lc);
if (rte->rtekind == RTE_VALUES && rte->inFromCl) {
if (result != NULL)
return NULL;
result = rte;
} else if (rte->rtekind == RTE_RELATION && !rte->inFromCl)
continue;
else
return NULL;
}
* We don't need to check the targetlist in any great detail, because
* parser/analyze.c will never generate a "bare" VALUES RTE --- they only
* appear inside auto-generated sub-queries with very restricted
* structure. However, DefineView might have modified the tlist by
* injecting new column aliases; so compare tlist resnames against the
* RTE's names to detect that.
*/
if (result != NULL) {
ListCell* lcn = NULL;
if (list_length(query->targetList) != list_length(result->eref->colnames))
return NULL;
forboth(lc, query->targetList, lcn, result->eref->colnames)
{
TargetEntry* tle = (TargetEntry*)lfirst(lc);
char* cname = strVal(lfirst(lcn));
if (tle->resjunk)
return NULL;
if (tle->resname == NULL || strcmp(tle->resname, cname) != 0)
return NULL;
}
}
return result;
}
static inline void get_hint_string_internal(const List* list, StringInfo buf)
{
ListCell* lc = NULL;
Hint* hint = NULL;
foreach (lc, list) {
hint = (Hint*)lfirst(lc);
appendStringInfo(buf, "%s", descHint(hint));
}
}
* @Description: Convert hint to string
* @in hstate: hint state.
* @out buf: String buf.
*/
void get_hint_string(HintState* hstate, StringInfo buf)
{
if (hstate == NULL) {
return;
}
ListCell* lc = NULL;
Hint* hint = NULL;
Assert(buf != NULL);
appendStringInfo(buf, "/*+");
get_hint_string_internal(hstate->join_hint, buf);
get_hint_string_internal(hstate->leading_hint, buf);
get_hint_string_internal(hstate->row_hint, buf);
get_hint_string_internal(hstate->stream_hint, buf);
get_hint_string_internal(hstate->block_name_hint, buf);
get_hint_string_internal(hstate->scan_hint, buf);
get_hint_string_internal(hstate->predpush_hint, buf);
get_hint_string_internal(hstate->predpush_same_level_hint, buf);
get_hint_string_internal(hstate->rewrite_hint, buf);
get_hint_string_internal(hstate->gather_hint, buf);
get_hint_string_internal(hstate->cache_plan_hint, buf);
get_hint_string_internal(hstate->set_hint, buf);
get_hint_string_internal(hstate->no_expand_hint, buf);
get_hint_string_internal(hstate->no_gpc_hint, buf);
foreach (lc, hstate->skew_hint) {
hint = (Hint*)lfirst(lc);
if (IsA(hint, SkewHintTransf)) {
SkewHintTransf* hint_transf = (SkewHintTransf*)hint;
hint = (Hint*)hint_transf->before;
}
appendStringInfo(buf, "%s", descHint(hint));
}
if (hstate->multi_node_hint) {
appendStringInfo(buf, " multinode ");
}
appendStringInfo(buf, "*/");
}
static void get_basic_select_query(Query* query, deparse_context* context, TupleDesc resultDesc)
{
StringInfo buf = context->buf;
RangeTblEntry* values_rte = NULL;
char* sep = NULL;
ListCell* l = NULL;
if (PRETTY_INDENT(context)) {
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoChar(buf, ' ');
}
* If the query looks like SELECT * FROM (VALUES ...), then print just the
* VALUES part. This reverses what transformValuesClause() did at parse
* time.
*/
values_rte = get_simple_values_rte(query);
if (values_rte != NULL) {
get_values_def(values_rte->values_lists, context);
return;
}
* Build up the query string - first we say SELECT
*/
appendStringInfo(buf, "SELECT");
get_hint_string(query->hintState, buf);
if (query->distinctClause != NIL) {
if (query->hasDistinctOn) {
appendStringInfo(buf, " DISTINCT ON (");
sep = "";
foreach (l, query->distinctClause) {
SortGroupClause* srt = (SortGroupClause*)lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList, false, context);
sep = ", ";
}
appendStringInfo(buf, ")");
} else
appendStringInfo(buf, " DISTINCT");
}
get_target_list(query, query->targetList, context, resultDesc);
get_from_clause(query, " FROM ", context, NIL, false);
if (query->jointree->quals != NULL) {
appendContextKeyword(context, " WHERE ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
if (query->groupClause != NULL) {
appendContextKeyword(context, " GROUP BY ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
if (query->groupingSets == NIL) {
sep = "";
foreach (l, query->groupClause) {
SortGroupClause* grp = (SortGroupClause*)lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList, false, context);
sep = ", ";
}
} else {
sep = "";
foreach (l, query->groupingSets) {
GroupingSet* grp = (GroupingSet*)lfirst(l);
appendStringInfoString(buf, sep);
get_rule_groupingset(grp, query->targetList, context);
sep = ", ";
}
}
query->vec_output = false;
}
if (query->havingQual != NULL) {
appendContextKeyword(context, " HAVING ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
if (IsA(query->havingQual, List)) {
StringInfo buf = context->buf;
char* sep = NULL;
ListCell* l = NULL;
sep = "";
foreach (l, (List*)query->havingQual) {
appendStringInfoString(buf, sep);
appendStringInfoString(buf, "(");
get_rule_expr((Node*)lfirst(l), context, false);
appendStringInfoString(buf, ")");
sep = " AND ";
}
} else
get_rule_expr(query->havingQual, context, false);
}
if (query->windowClause != NIL) {
get_rule_windowclause(query, context);
query->vec_output = false;
}
}
* get_target_list - Parse back a SELECT target list
*
* This is also used for RETURNING lists in INSERT/UPDATE/DELETE.
* ----------
*/
static void get_target_list(Query* query, List* targetList, deparse_context* context, TupleDesc resultDesc)
{
StringInfo buf = context->buf;
StringInfoData targetbuf;
bool last_was_multiline = false;
char* sep = NULL;
int colno;
ListCell* l = NULL;
ListCell* star_start_cell = NULL;
ListCell* star_end_cell = NULL;
ListCell* star_only_cell = NULL;
int star_start = -1;
int star_end = -1;
int star_only = -1;
#ifdef PGXC
bool no_targetlist = true;
#endif
Assert(list_length(query->starStart) == list_length(query->starEnd));
Assert(list_length(query->starStart) == list_length(query->starOnly));
star_start_cell = list_head(query->starStart);
star_end_cell = list_head(query->starEnd);
star_only_cell = list_head(query->starOnly);
initStringInfo(&targetbuf);
sep = " ";
colno = 0;
foreach (l, targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(l);
char* colname = NULL;
char* attname = NULL;
bool for_star = false;
if (tle->resjunk)
continue;
if (query->hasRecursive && IsPseudoReturnTargetEntry(tle)) {
continue;
}
#ifdef PGXC
if (no_targetlist)
no_targetlist = false;
#endif
* dump view def, we dump * instead expanded target entry because expanded target entrys
* mabye have same alias name which is figured by function FigureColnameInternal(), and
* views will be fail to be recreate due to duplicate alias names, just as following case
*
*create view test_view(col1,col2) as
*SELECT * FROM
*(
* SELECT
* CAST(a/10000 AS DECIMAL(18,2)),
* CAST(CAST(b AS DECIMAL(18,4))/a*100 AS DECIMAL(18,2))
* FROM test_tb
*);
*
* dump as:
*
*CREATE VIEW test_view AS
* SELECT
* __unnamed_subquery__."numeric" AS col1,
* __unnamed_subquery__."numeric" AS col2 FROM
* (
* SELECT
* ((test_tb.a / 10000))::numeric(18,2) AS "numeric",
* ((((test_tb.b)::numeric(18,4) / (test_tb.a)::numeric) * (100)::numeric))::numeric(18,2) AS
*"numeric" FROM test_tb ) __unnamed_subquery__;
*/
if (context->viewdef || (context->is_fqs && query->use_star_targets)) {
if (star_start > 0) {
if (star_end > tle->resno) {
colno++;
continue;
}
star_start = -1;
star_end = -1;
star_only = -1;
}
if (0 > star_start) {
if (star_start_cell && lfirst_int(star_start_cell) == tle->resno) {
for_star = true;
star_start = lfirst_int(star_start_cell);
star_end = lfirst_int(star_end_cell);
star_only = lfirst_int(star_only_cell);
star_start_cell = lnext(star_start_cell);
star_end_cell = lnext(star_end_cell);
star_only_cell = lnext(star_only_cell);
}
}
}
appendStringInfoString(buf, sep);
sep = ", ";
colno++;
if (for_star && star_only > 0) {
appendStringInfo(buf, " *");
continue;
}
* Put the new field text into targetbuf so we can decide after we've
* got it whether or not it needs to go on a new line.
*/
resetStringInfo(&targetbuf);
context->buf = &targetbuf;
* We special-case Var nodes rather than using get_rule_expr. This is
* needed because get_rule_expr will display a whole-row Var as
* "foo.*", which is the preferred notation in most contexts, but at
* the top level of a SELECT list it's not right (the parser will
* expand that notation into multiple columns, yielding behavior
* different from a whole-row Var). We need to call get_variable
* directly so that we can tell it to do the right thing.
*/
if (tle->expr && IsA(tle->expr, Var)) {
attname = get_variable((Var*)tle->expr, 0, true, context, for_star);
} else {
* Attname not always be "?column?" for no var type.
* For example:
* select * from (select '', val from test);
* NULL alias is "unknow" rather then "?column?".
*
* In this case, we need add as alias.
*/
get_rule_expr((Node*)tle->expr, context, true);
}
* Figure out what the result column should be called. In the context
* of a view, use the view's tuple descriptor (so as to pick up the
* effects of any column RENAME that's been done on the view).
* Otherwise, just use what we can find in the TLE.
*/
if (resultDesc && colno <= resultDesc->natts)
colname = NameStr(resultDesc->attrs[colno - 1].attname);
else
colname = tle->resname;
if (colname && !for_star)
{
if (attname == NULL || strcmp(attname, colname) != 0)
appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
}
if (attname != NULL) {
pfree_ext(attname);
attname = NULL;
}
context->buf = buf;
if (PRETTY_INDENT(context) && context->wrapColumn >= 0) {
int leading_nl_pos = -1;
char* trailing_nl = NULL;
int pos;
for (pos = 0; pos < targetbuf.len; pos++) {
if (targetbuf.data[pos] == '\n') {
leading_nl_pos = pos;
break;
}
if (targetbuf.data[pos] != ' ')
break;
}
trailing_nl = strrchr(buf->data, '\n');
if (trailing_nl == NULL)
trailing_nl = buf->data;
else
trailing_nl++;
* If the field we're adding is the first in the list, or it
* already has a leading newline, don't add anything. Otherwise,
* add a newline, plus some indentation, if either the new field
* would cause an overflow or the last field used more than one
* line.
*/
if (colno > 1 && leading_nl_pos == -1 &&
((strlen(trailing_nl) + strlen(targetbuf.data) > (unsigned int)(context->wrapColumn)) ||
last_was_multiline))
appendContextKeyword(context, "", -PRETTYINDENT_STD, PRETTYINDENT_STD, PRETTYINDENT_VAR);
last_was_multiline = (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
}
appendStringInfoString(buf, targetbuf.data);
}
#ifdef PGXC
* Because the empty target list can generate invalid SQL
* clause. Here, just fill a '*' to process a table without
* any columns, this statement will be sent to Datanodes
* and treated correctly on remote nodes.
*/
if (no_targetlist)
appendStringInfo(buf, " *");
#endif
pfree_ext(targetbuf.data);
}
static void get_setop_query(Node* setOp, Query* query, deparse_context* context, TupleDesc resultDesc)
{
StringInfo buf = context->buf;
bool need_paren = false;
CHECK_FOR_INTERRUPTS();
check_stack_depth();
if (IsA(setOp, RangeTblRef)) {
RangeTblRef* rtr = (RangeTblRef*)setOp;
RangeTblEntry* rte = rt_fetch(rtr->rtindex, query->rtable);
Query* subquery = rte->subquery;
Assert(subquery != NULL);
need_paren = (subquery->cteList || subquery->sortClause || subquery->rowMarks || subquery->limitOffset ||
subquery->limitCount || subquery->setOperations);
if (need_paren)
appendStringInfoChar(buf, '(');
if (subquery->setOperations == NULL) {
get_query_def(subquery,
buf,
context->namespaces,
resultDesc,
context->prettyFlags,
context->wrapColumn,
context->indentLevel
#ifdef PGXC
,
context->finalise_aggs,
context->sortgroup_colno,
context->parser_arg
#endif
,
context->qrw_phase,
context->viewdef,
context->is_fqs,
context->skip_lock);
} else {
if (context->qrw_phase)
get_setop_query(subquery->setOperations, subquery, context, resultDesc);
else
get_query_def(subquery,
buf,
context->namespaces,
resultDesc,
context->prettyFlags,
context->wrapColumn,
context->indentLevel
#ifdef PGXC
,
context->finalise_aggs,
context->sortgroup_colno,
context->parser_arg
#endif
,
context->qrw_phase,
context->viewdef,
context->is_fqs,
context->skip_lock);
}
if (need_paren)
appendStringInfoChar(buf, ')');
} else if (IsA(setOp, SetOperationStmt)) {
SetOperationStmt* op = (SetOperationStmt*)setOp;
if (PRETTY_INDENT(context)) {
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoSpaces(buf, PRETTYINDENT_STD);
}
* We force parens whenever nesting two SetOperationStmts. There are
* some cases in which parens are needed around a leaf query too, but
* those are more easily handled at the next level down (see code
* above).
*/
need_paren = !IsA(op->larg, RangeTblRef);
if (need_paren)
appendStringInfoChar(buf, '(');
get_setop_query(op->larg, query, context, resultDesc);
if (need_paren)
appendStringInfoChar(buf, ')');
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
switch (op->op) {
case SETOP_UNION:
appendContextKeyword(context, "UNION ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case SETOP_INTERSECT:
appendContextKeyword(context, "INTERSECT ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case SETOP_EXCEPT:
appendContextKeyword(context, "EXCEPT ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
default:
ereport(
ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized set op: %d", (int)op->op)));
}
if (op->all)
appendStringInfo(buf, "ALL ");
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
need_paren = !IsA(op->rarg, RangeTblRef);
if (need_paren)
appendStringInfoChar(buf, '(');
get_setop_query(op->rarg, query, context, resultDesc);
if (need_paren)
appendStringInfoChar(buf, ')');
if (PRETTY_INDENT(context))
context->indentLevel -= PRETTYINDENT_STD;
} else {
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized node type: %d", (int)nodeTag(setOp))));
}
}
* Display a sort/group clause.
*
* Also returns the expression tree, so caller need not find it again.
*/
static Node* get_rule_sortgroupclause(Index ref, List* tlist, bool force_colno, deparse_context* context)
{
StringInfo buf = context->buf;
TargetEntry* tle = NULL;
Node* expr = NULL;
tle = get_sortgroupref_tle(ref, tlist);
expr = (Node*)tle->expr;
* Use column-number form if requested by caller. Otherwise, if
* expression is a constant, force it to be dumped with an explicit cast
* as decoration --- this is because a simple integer constant is
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
* dump it without any decoration. Otherwise, just dump the expression
* normally.
*/
if (force_colno || context->sortgroup_colno) {
Assert(!tle->resjunk);
appendStringInfo(buf, "%d", tle->resno);
} else if (expr && IsA(expr, Const))
get_const_expr((Const*)expr, context, 1);
else
get_rule_expr(expr, context, true);
return expr;
}
#ifdef USE_SPQ
* Display a sort/group clause.
*
* Also returns the expression tree, so caller need not find it again.
*/
static Node* get_rule_sortgroupclause_spq(Index ref, bool force_colno, deparse_context* context)
{
StringInfo buf = context->buf;
TargetEntry* tle = NULL;
Node* expr = NULL;
List* tlist;
deparse_namespace* dpns_spq = (deparse_namespace*)linitial(context->namespaces);
PlanState* ps = dpns_spq->planstate;
WindowAgg* node = NULL;
node = (WindowAgg*)ps->plan;
tlist = node->plan.lefttree->targetlist;
if (tlist == NULL){
return expr;
}
tle = get_sortgroupref_tle_spq(ref, tlist);
expr = (Node*)tle->expr;
deparse_namespace* dpns = NULL;
deparse_namespace save_dpns;
dpns = (deparse_namespace*)list_nth(context->namespaces, ((Var*)expr)->varlevelsup);
push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
* Use column-number form if requested by caller. Otherwise, if
* expression is a constant, force it to be dumped with an explicit cast
* as decoration --- this is because a simple integer constant is
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
* dump it without any decoration. Otherwise, just dump the expression
* normally.
*/
if (force_colno || context->sortgroup_colno) {
Assert(!tle->resjunk);
appendStringInfo(buf, "%d", tle->resno);
} else if (expr && IsA(expr, Var))
get_rule_expr(expr, context, true);
pop_child_plan(dpns, &save_dpns);
return expr;
}
#endif
* @Description: Display a GroupingSet.
* @in gset: Grouping Set.
* @in targetlist: targetlist.
* @in context: Context info needed for invoking a recursive querytree display routine.
*/
static void get_rule_groupingset(GroupingSet* gset, List* targetlist, deparse_context* context)
{
ListCell* l = NULL;
StringInfo buf = context->buf;
bool omit_child_parens = true;
char* sep = "";
switch (gset->kind) {
case GROUPING_SET_EMPTY:
appendStringInfoString(buf, "()");
return;
case GROUPING_SET_SIMPLE: {
if (list_length(gset->content) != 1)
appendStringInfoString(buf, "(");
foreach (l, gset->content) {
Index ref = lfirst_int(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(ref, targetlist, false, context);
sep = ", ";
}
if (list_length(gset->content) != 1)
appendStringInfoString(buf, ")");
}
return;
case GROUPING_SET_ROLLUP:
appendStringInfoString(buf, "ROLLUP(");
break;
case GROUPING_SET_CUBE:
appendStringInfoString(buf, "CUBE(");
break;
case GROUPING_SET_SETS:
appendStringInfoString(buf, "GROUPING SETS (");
omit_child_parens = false;
break;
default:
break;
}
foreach (l, gset->content) {
appendStringInfoString(buf, sep);
get_rule_groupingset((GroupingSet*)lfirst(l), targetlist, context);
sep = ", ";
}
appendStringInfoString(buf, ")");
}
static void get_rule_separator(Const* con, StringInfo buf)
{
Oid typoutput;
char* extval = NULL;
bool typIsVarlena = false;
appendStringInfoString(buf, "\'");
if (u_sess->exec_cxt.under_auto_explain) {
appendStringInfoString(buf, "***");
} else if (!con->constisnull) {
getTypeOutputInfo(con->consttype, &typoutput, &typIsVarlena);
extval = OidOutputFunctionCall(typoutput, con->constvalue);
appendStringInfoString(buf, extval);
pfree_ext(extval);
}
appendStringInfoChar(buf, '\'');
}
* Display an ORDER BY list.
*/
static void get_rule_orderby(List* orderList, List* targetList, bool force_colno, deparse_context* context)
{
StringInfo buf = context->buf;
const char* sep = NULL;
ListCell* l = NULL;
sep = "";
foreach (l, orderList) {
SortGroupClause* srt = (SortGroupClause*)lfirst(l);
Node* sortexpr = NULL;
Oid sortcoltype;
TypeCacheEntry* typentry = NULL;
appendStringInfoString(buf, sep);
#ifdef USE_SPQ
if (IS_SPQ_COORDINATOR && (list_length(context->windowClause) > 0) &&
lfirst(list_head(context->windowClause)) != NULL &&
((WindowClause *)lfirst(list_head(context->windowClause)))->reOrderSPQ) {
sortexpr = get_rule_sortgroupclause_spq(srt->tleSortGroupRef, force_colno, context);
} else
#endif
sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList, force_colno, context);
sortcoltype = exprType(sortexpr);
typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
if (srt->sortop == typentry->lt_opr) {
if (srt->nulls_first)
appendStringInfo(buf, " NULLS FIRST");
} else if (srt->sortop == typentry->gt_opr) {
appendStringInfo(buf, " DESC");
if (!srt->nulls_first)
appendStringInfo(buf, " NULLS LAST");
} else {
appendStringInfo(buf, " USING %s", generate_operator_name(srt->sortop, sortcoltype, sortcoltype));
if (srt->nulls_first)
appendStringInfo(buf, " NULLS FIRST");
else
appendStringInfo(buf, " NULLS LAST");
}
sep = ", ";
}
}
* Display a WINDOW clause.
*
* Note that the windowClause list might contain only anonymous window
* specifications, in which case we should print nothing here.
*/
static void get_rule_windowclause(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
const char* sep = NULL;
ListCell* l = NULL;
sep = NULL;
foreach (l, query->windowClause) {
WindowClause* wc = (WindowClause*)lfirst(l);
if (wc->name == NULL)
continue;
if (sep == NULL)
appendContextKeyword(context, " WINDOW ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
else
appendStringInfoString(buf, sep);
appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
get_rule_windowspec(wc, query->targetList, context);
sep = ", ";
}
}
* Display a window definition
*/
static void get_rule_windowspec(WindowClause* wc, List* targetList, deparse_context* context)
{
StringInfo buf = context->buf;
bool needspace = false;
const char* sep = NULL;
ListCell* l = NULL;
appendStringInfoChar(buf, '(');
if (wc->refname) {
appendStringInfoString(buf, quote_identifier(wc->refname));
needspace = true;
}
if (wc->partitionClause && !wc->refname) {
if (needspace)
appendStringInfoChar(buf, ' ');
appendStringInfoString(buf, "PARTITION BY ");
sep = "";
foreach (l, wc->partitionClause) {
SortGroupClause* grp = (SortGroupClause*)lfirst(l);
appendStringInfoString(buf, sep);
#ifdef USE_SPQ
if (IS_SPQ_COORDINATOR && wc->rePartitionSPQ) {
get_rule_sortgroupclause_spq(grp->tleSortGroupRef, false, context);
} else
#endif
get_rule_sortgroupclause(grp->tleSortGroupRef, targetList, false, context);
sep = ", ";
}
needspace = true;
}
if (wc->orderClause && !wc->copiedOrder) {
if (needspace)
appendStringInfoChar(buf, ' ');
appendStringInfoString(buf, "ORDER BY ");
get_rule_orderby(wc->orderClause, targetList, false, context);
needspace = true;
}
if (wc->frameOptions & FRAMEOPTION_NONDEFAULT) {
if (needspace)
appendStringInfoChar(buf, ' ');
if (wc->frameOptions & FRAMEOPTION_RANGE)
appendStringInfoString(buf, "RANGE ");
else if (wc->frameOptions & FRAMEOPTION_ROWS)
appendStringInfoString(buf, "ROWS ");
else
Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
appendStringInfoString(buf, "BETWEEN ");
if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_START_VALUE) {
get_rule_expr(wc->startOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING ");
else
Assert(false);
} else
Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN) {
appendStringInfoString(buf, "AND ");
if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_END_VALUE) {
get_rule_expr(wc->endOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_END_VALUE_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING ");
else
Assert(false);
} else
Assert(false);
}
buf->len--;
}
appendStringInfoChar(buf, ')');
}
* Display a window definition for listagg
*/
static void get_rule_windowspec_listagg(WindowClause* wc, List* targetList, deparse_context* context)
{
StringInfo buf = context->buf;
bool needspace = false;
const char* sep = NULL;
ListCell* l = NULL;
appendStringInfoChar(buf, '(');
if (wc->refname) {
appendStringInfoString(buf, quote_identifier(wc->refname));
needspace = true;
}
if (wc->orderClause && !wc->copiedOrder) {
if (needspace)
appendStringInfoChar(buf, ' ');
appendStringInfoString(buf, "ORDER BY ");
get_rule_orderby(wc->orderClause, targetList, false, context);
needspace = true;
}
appendStringInfoString(buf, ") OVER (");
if (wc->partitionClause && !wc->refname) {
if (needspace)
appendStringInfoChar(buf, ' ');
appendStringInfoString(buf, "PARTITION BY ");
sep = "";
foreach (l, wc->partitionClause) {
SortGroupClause* grp = (SortGroupClause*)lfirst(l);
appendStringInfoString(buf, sep);
#ifdef USE_SPQ
if (IS_SPQ_COORDINATOR && wc->rePartitionSPQ) {
get_rule_sortgroupclause_spq(grp->tleSortGroupRef, false, context);
} else
#endif
get_rule_sortgroupclause(grp->tleSortGroupRef, targetList, false, context);
sep = ", ";
}
needspace = true;
}
appendStringInfoChar(buf, ')');
}
* @Description: get_set_target_list Parse back a SET clause
* This is used for UPDATE and INSERT ON DUPLICATE KEY UPDATE.
*/
static void get_set_target_list(List* targetList, RangeTblEntry* rte, deparse_context* context)
{
ListCell* lc = NULL;
char* sep = NULL;
StringInfo buf = context->buf;
sep = "";
foreach (lc, targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(lc);
Node* expr = NULL;
if (tle->resjunk)
continue;
appendStringInfoString(buf, sep);
sep = ", ";
* Put out name of target column; look in the catalogs, not at
* tle->resname, since resname will fail to track RENAME.
*/
appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resno)));
* Print any indirection needed (subfields or subscripts), and strip
* off the top-level nodes representing the indirection assignments.
*/
expr = processIndirection((Node*)tle->expr, context, true);
appendStringInfo(buf, " = ");
get_rule_expr(expr, context, false);
}
}
* get_insert_query_def - Parse back an INSERT parsetree
* ----------
*/
static void get_insert_query_def(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
RangeTblEntry* select_rte = NULL;
RangeTblEntry* values_rte = NULL;
RangeTblEntry* rte = NULL;
char* sep = NULL;
ListCell* values_cell = NULL;
ListCell* l = NULL;
List* strippedexprs = NIL;
bool is_fqs_inselect = false;
get_with_clause(query, context);
#ifdef PGXC
* In the case of "INSERT ... DEFAULT VALUES" analyzed in pgxc planner,
* return the sql statement directly if the table has no default values.
*/
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && !query->targetList) {
appendStringInfo(buf, "%s", query->sql_statement);
return;
}
* select_rte are not required by INSERT queries in XC
* it should stay null for INSERT queries to work corretly
* consider an example
* insert into tt select * from tt
* This query uses select_rte, but again that is not required in XC
* Again here the query gets broken down into two queries
* SELECT column1, column2 FROM ONLY tt WHERE true
* and
* INSERT INTO tt (column1, column2) VALUES ($1, $2)
* Note again that the insert query does not need select_rte
* Hence we keep both select_rte and values_rte NULL.
*/
if (context->is_fqs || !(IS_PGXC_COORDINATOR && !IsConnFromCoord())) {
#endif
* If it's an INSERT ... SELECT or multi-row VALUES, there will be a
* single RTE for the SELECT or VALUES. Plain VALUES has neither.
*/
foreach (l, query->rtable) {
rte = (RangeTblEntry*)lfirst(l);
if (rte->rtekind == RTE_SUBQUERY && !(rte->pulled_from_subquery)) {
is_fqs_inselect = true;
if (select_rte != NULL) {
ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), errmsg("too many subquery RTEs in INSERT")));
}
select_rte = rte;
}
}
#ifdef PGXC
}
#endif
* values_rte will be required by INSERT queries in XC
* only when the relation is located on a single node
* requested by FQS
* Consider an example
* CREATE NODE GROUP ng WITH (datanode2);
* CREATE TABLE tt (column1 int4, column2 text) TO GROUP ng;
* INSERT INTO tt (column1) VALUES(1), (2)
*
* for other cases values_rte should stay null
* create table tt as values(1,'One'),(2,'Two');
* This query uses values_rte, but we do not need them in XC
* because it gets broken down into two queries
* CREATE TABLE tt(column1 int4, column2 text)
* and
* INSERT INTO tt (column1, column2) VALUES ($1, $2)
* Note that the insert query does not need values_rte
*/
#ifdef PGXC
if ((context->is_fqs && !is_fqs_inselect) || !(IS_PGXC_COORDINATOR && !IsConnFromCoord())) {
#endif
foreach (l, query->rtable) {
rte = (RangeTblEntry*)lfirst(l);
if (rte->rtekind == RTE_VALUES) {
if (values_rte != NULL) {
ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), errmsg("too many values RTEs in INSERT")));
}
values_rte = rte;
}
}
#ifdef PGXC
}
#endif
if ((select_rte != NULL) && (values_rte != NULL)) {
ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), errmsg("both subquery and values RTEs in INSERT")));
}
* Start the query with INSERT INTO relname
*/
rte = rt_fetch(linitial_int(query->resultRelations), query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context)) {
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoChar(buf, ' ');
}
appendStringInfo(buf, "INSERT ");
get_hint_string(query->hintState, buf);
appendStringInfo(buf, "INTO %s ", generate_relation_name(rte->relid, NIL));
if (t_thrd.proc->workingVersionNum >= UPSERT_WHERE_VERSION_NUM) {
if (rte->alias != NULL) {
appendStringInfo(buf, "AS %s ", quote_identifier(rte->alias->aliasname));
} else if (rte->eref != NULL && query->upsertClause != NULL) {
appendStringInfo(buf, "AS %s ", quote_identifier(rte->eref->aliasname));
}
}
* Add the insert-column-names list. To handle indirection properly, we
* need to look for indirection nodes in the top targetlist (if it's
* INSERT ... SELECT or INSERT ... single VALUES), or in the first
* expression list of the VALUES RTE (if it's INSERT ... multi VALUES). We
* assume that all the expression lists will have similar indirection in
* the latter case.
*/
if (values_rte != NULL) {
values_cell = list_head((List*)linitial(values_rte->values_lists));
} else {
values_cell = NULL;
}
strippedexprs = NIL;
sep = "";
if (query->targetList) {
appendStringInfoChar(buf, '(');
}
bool is_fqs_insert = false;
ListCell* ll = NULL;
Index std_varno = 0;
int pos = 0;
if ((context->is_fqs && is_fqs_inselect) || (context->is_fqs && values_rte != NULL)) {
is_fqs_insert = true;
}
foreach (l, query->targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(l);
AttrNumber tle_resno = tle->resno;
bool col_with_default = true;
if (tle->resjunk)
continue;
* Put out name of target column; look in the catalogs, not at
* tle->resname, since resname will fail to track RENAME.
*/
if (is_fqs_insert) {
pos++;
foreach (ll, query->targetList) {
TargetEntry* tmptle = (TargetEntry*)lfirst(ll);
Var* var = get_var_from_node((Node*)(tmptle->expr), func_oid_check_pass);
if (var == NULL)
continue;
if (std_varno == 0)
std_varno = var->varno;
if (var->varattno == pos && var->varno == std_varno) {
tle_resno = tmptle->resno;
col_with_default = false;
break;
}
}
* Example:"create table t1(a int, b int default , c int)distribute by hash(a);
* insert into t1(a,c) values(1,1),(1,1);"
* Columns with default should not be reversely parsed.
* It'll go wrong in DN. For example, It will be resolved as
* insert into t1(a,b,c) values(1,1),(1,1);
*/
if (col_with_default)
continue;
}
appendStringInfoString(buf, sep);
sep = ", ";
const char* attr_str = quote_identifier(get_relid_attribute_name(rte->relid, tle_resno));
appendStringInfoString(buf, attr_str);
* Print any indirection needed (subfields or subscripts), and strip
* off the top-level nodes representing the indirection assignments.
*/
if (values_cell != NULL) {
processIndirection((Node*)lfirst(values_cell), context, true);
values_cell = lnext(values_cell);
} else {
if (IsA((Node*)tle->expr, FieldStore)) {
ListCell* l1 = NULL;
ListCell* l2 = NULL;
FieldStore* fstore = (FieldStore*)tle->expr;
Assert(list_length(fstore->newvals) == list_length(fstore->fieldnums));
forboth(l1, fstore->newvals, l2, fstore->fieldnums) {
FieldStore* single_fstore = NULL;
single_fstore = makeNode(FieldStore);
single_fstore->arg = (Expr*)fstore->arg;
single_fstore->newvals = list_make1(lfirst(l1));
single_fstore->fieldnums = list_make1_int(lfirst_int(l2));
single_fstore->resulttype = fstore->resulttype;
strippedexprs = lappend(strippedexprs, processIndirection((Node*)single_fstore, context, true));
if (lnext(l1) != NULL) {
appendStringInfoString(buf, sep);
appendStringInfoString(buf, attr_str);
}
pfree(single_fstore->newvals);
single_fstore->newvals = NULL;
pfree(single_fstore->fieldnums);
single_fstore->fieldnums = NULL;
pfree(single_fstore);
single_fstore = NULL;
}
} else if (IsA((Node*)tle->expr, ArrayRef)) {
Node* aref = (Node*)tle->expr;
for (;;) {
strippedexprs = lappend(strippedexprs, processIndirection(aref, context, true));
* expand multiple entries for the same target attribute if need.
* if this is the last one, we don't append sep and column msg.
*/
if (IsA(((ArrayRef*)aref)->refexpr, ArrayRef)) {
appendStringInfoString(buf, sep);
appendStringInfoString(buf, attr_str);
aref = (Node*)((ArrayRef*)aref)->refexpr;
} else {
break;
}
}
} else {
strippedexprs = lappend(strippedexprs, (Node*)tle->expr);
}
}
pfree_ext(attr_str);
}
if (query->targetList) {
appendStringInfo(buf, ") ");
}
if (select_rte != NULL) {
get_query_def(select_rte->subquery, buf, NIL, NULL, context->prettyFlags,
context->wrapColumn, context->indentLevel,
#ifdef PGXC
context->finalise_aggs, context->sortgroup_colno, context->parser_arg,
#endif
context->qrw_phase, context->viewdef, context->is_fqs);
} else if (values_rte != NULL) {
get_values_def(values_rte->values_lists, context);
} else if (strippedexprs != NULL) {
appendContextKeyword(context, "VALUES (", -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
get_rule_expr((Node*)strippedexprs, context, false);
appendStringInfoChar(buf, ')');
} else {
appendStringInfo(buf, "DEFAULT VALUES");
}
if (query->mergeActionList) {
appendStringInfo(buf, " ON DUPLICATE KEY UPDATE ");
ListCell* l = NULL;
bool update = false;
foreach (l, query->mergeActionList) {
MergeAction* mc = (MergeAction*)lfirst(l);
if (mc->commandType == CMD_UPDATE) {
update = true;
get_set_target_list(mc->targetList, rte, context);
} else {
continue;
}
}
if (!update) {
appendStringInfoString(buf, "NOTHING");
}
}
if (query->upsertClause != NULL && query->upsertClause->upsertAction != UPSERT_NONE) {
appendStringInfoString(buf, " ON DUPLICATE KEY UPDATE ");
UpsertExpr* upsertClause = query->upsertClause;
if (upsertClause->upsertAction == UPSERT_NOTHING) {
appendStringInfoString(buf, "NOTHING");
} else {
Assert(!context->is_upsert_clause);
context->is_upsert_clause = true;
get_update_query_targetlist_def(query, upsertClause->updateTlist, rte, context);
context->is_upsert_clause = false;
if (upsertClause->upsertWhere != NULL) {
appendContextKeyword(context, " WHERE ", PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
if (IsA(upsertClause->upsertWhere, List)) {
Expr* expr = make_ands_explicit((List*)upsertClause->upsertWhere);
get_rule_expr((Node*)expr, context, false);
} else {
get_rule_expr(upsertClause->upsertWhere, context, false);
}
}
}
}
if (query->returningList) {
appendContextKeyword(context, " RETURNING", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query, query->returningList, context, NULL);
}
list_free_ext(strippedexprs);
}
* get_update_query_def - Parse back an UPDATE parsetree
* ----------
*/
static void get_update_query_def(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
RangeTblEntry* rte = NULL;
get_with_clause(query, context);
* Start the query with UPDATE relname SET
*/
rte = rt_fetch(linitial_int(query->resultRelations), query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context)) {
appendStringInfoChar(buf, ' ');
context->indentLevel += PRETTYINDENT_STD;
}
appendStringInfo(buf, "UPDATE ");
get_hint_string(query->hintState, buf);
appendStringInfo(buf, "%s%s", only_marker(rte), generate_relation_name(rte->relid, NIL));
if (rte->alias != NULL) {
appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname));
}
appendStringInfoString(buf, " SET ");
get_update_query_targetlist_def(query, query->targetList, rte, context);
get_from_clause(query, " FROM ", context);
if (query->jointree->quals != NULL) {
appendContextKeyword(context, " WHERE ", PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
if (query->returningList) {
appendContextKeyword(context, " RETURNING", PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query, query->returningList, context, NULL);
}
}
* get_update_query_targetlist_def - Parse back an UPDATE targetlist
* ----------
*/
static void get_update_query_targetlist_def(Query* query, List* targetList,
RangeTblEntry* rte, deparse_context* context)
{
StringInfo buf = context->buf;
ListCell* l = NULL;
const char* sep;
sep = "";
foreach(l, targetList) {
TargetEntry* tle = (TargetEntry*)lfirst(l);
Node* expr = NULL;
if (tle->resjunk)
continue;
appendStringInfoString(buf, sep);
sep = ", ";
* Put out name of target column; look in the catalogs, not at
* tle->resname, since resname will fail to track RENAME.
*/
appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resno)));
* Print any indirection needed (subfields or subscripts), and strip
* off the top-level nodes representing the indirection assignments.
*/
if (IsA((Node*)tle->expr, FieldStore)) {
ListCell* l1 = NULL;
ListCell* l2 = NULL;
FieldStore* fstore = (FieldStore*)tle->expr;
FieldStore* single_fstore = makeNode(FieldStore);
single_fstore->arg = (Expr*)fstore->arg;
single_fstore->resulttype = fstore->resulttype;
Assert(list_length(fstore->newvals) == list_length(fstore->fieldnums));
forboth(l1, fstore->newvals, l2, fstore->fieldnums)
{
single_fstore->newvals = list_make1(lfirst(l1));
single_fstore->fieldnums = list_make1_int(lfirst_int(l2));
expr = processIndirection((Node*)single_fstore, context, true);
appendStringInfo(buf, " = ");
get_rule_expr(expr, context, false);
if (lnext(l1) != NULL) {
appendStringInfoString(buf, sep);
appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resno)));
}
pfree(single_fstore->newvals);
single_fstore->newvals = NULL;
pfree(single_fstore->fieldnums);
single_fstore->fieldnums = NULL;
}
pfree(single_fstore);
single_fstore = NULL;
} else if (IsA((Node*)tle->expr, ArrayRef)) {
Node* aref = (Node*)tle->expr;
for (;;) {
expr = processIndirection(aref, context, true);
appendStringInfo(buf, " = ");
get_rule_expr(expr, context, false);
* expand multiple entries for the same target attribute if need
* if this is the last one, we don't append sep and column msg.
*/
if (IsA(((ArrayRef*)aref)->refexpr, ArrayRef)) {
appendStringInfoString(buf, sep);
appendStringInfoString(buf, quote_identifier(get_relid_attribute_name(rte->relid, tle->resno)));
aref = (Node*)((ArrayRef*)aref)->refexpr;
} else {
break;
}
}
} else {
appendStringInfo(buf, " = ");
get_rule_expr((Node*)tle->expr, context, false);
}
}
}
* get_delete_query_def - Parse back a DELETE parsetree
* ----------
*/
static void get_delete_query_def(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
RangeTblEntry* rte = NULL;
get_with_clause(query, context);
* Start the query with DELETE FROM relname
*/
rte = rt_fetch(linitial_int(query->resultRelations), query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context)) {
appendStringInfoChar(buf, ' ');
context->indentLevel += PRETTYINDENT_STD;
}
appendStringInfo(buf, "DELETE ");
get_hint_string(query->hintState, buf);
appendStringInfo(buf, "FROM %s%s", only_marker(rte), generate_relation_name(rte->relid, NIL));
if (rte->alias != NULL)
appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname));
get_from_clause(query, " USING ", context);
if (query->jointree->quals != NULL) {
appendContextKeyword(context, " WHERE ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
if (query->limitCount != NULL) {
appendContextKeyword(context, " LIMIT ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
get_rule_expr(query->limitCount, context, false);
}
if (query->returningList) {
appendContextKeyword(context, " RETURNING", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query, query->returningList, context, NULL);
}
}
* only support add colum and drop column, support text and numeric, number datatype,
* if timeseries table support more datatype, should test gs_expand if it works well
*/
void get_utility_stmt_def(AlterTableStmt* stmt, StringInfo buf)
{
Assert(stmt != NULL);
RangeVar* relation = stmt->relation;
appendStringInfo(buf, "ALTER TABLE ");
if (relation->schemaname && relation->schemaname[0]) {
appendStringInfo(buf, "%s.", quote_identifier(relation->schemaname));
}
appendStringInfo(buf, "%s", quote_identifier(relation->relname));
if (stmt->cmds) {
* now we only support have one action, does not support
* alter table cpu add column a numeric tsfield,add column c numeric tsfield;
*/
AlterTableCmd* cmd = (AlterTableCmd*)linitial(stmt->cmds);
switch (cmd->subtype) {
case AT_AddColumn: {
appendStringInfo(buf, " ADD COLUMN ");
ColumnDef* colDef = (ColumnDef*)cmd->def;
appendStringInfo(buf, " %s", colDef->colname);
char* columntype = NULL;
if (list_length(colDef->typname->names) == 2) {
char* colcatalog = strVal(linitial(colDef->typname->names));
if (strcmp(colcatalog, "pg_catalog") != 0) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unsupported column type when converting to string")));
}
columntype = strVal(lsecond(colDef->typname->names));
} else if (list_length(colDef->typname->names) == 1) {
columntype = strVal(linitial(colDef->typname->names));
} else {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unsupported column type when converting to string")));
}
appendStringInfo(buf, " %s", columntype);
if (colDef->kvtype == ATT_KV_FIELD) {
appendStringInfo(buf, " TSFIELD");
} else if (colDef->kvtype == ATT_KV_TAG) {
appendStringInfo(buf, " TSTAG");
} else if (colDef->kvtype == ATT_KV_TIMETAG) {
appendStringInfo(buf, " TSTIME");
}
} break;
case AT_DropColumn: {
appendStringInfo(buf, " DROP COLUMN %s", cmd->name);
if (cmd->behavior) {
appendStringInfo(buf, (cmd->behavior == DROP_CASCADE) ? " CASCADE" : " RESTRICT");
}
} break;
default:
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unsupported cmd alter table type %d", cmd->subtype)));
break;
}
} else {
appendStringInfo(buf, " REDISANYVALUE");
}
}
static void AppendDistributeByColDef(StringInfo buf, CreateStmt* stmt)
{
int i = 0;
ListCell *cell = NULL;
appendStringInfo(buf, "(");
foreach (cell, stmt->distributeby->colname) {
if (i == 0) {
appendStringInfo(buf, "%s", strVal(lfirst(cell)));
} else {
appendStringInfo(buf, ", %s", strVal(lfirst(cell)));
}
i++;
}
appendStringInfo(buf, ")");
}
static bool IsDefaultSlice(StringInfo buf, RangePartitionDefState* sliceDef)
{
ParseState* pstate = make_parsestate(NULL);
Node* valueNode = (Node *)list_nth(sliceDef->boundary, 0);
Const* valueConst = (Const *)transformExprRecurse(pstate, valueNode);
if (valueConst->ismaxvalue) {
appendStringInfo(buf, "DEFAULT");
free_parsestate(pstate);
return true;
} else {
free_parsestate(pstate);
return false;
}
}
char* EscapeQuotes(const char* src)
{
int len = strlen(src), i, j;
char* result = (char*)palloc(len * 2 + 1);
for (i = 0, j = 0; i < len; i++) {
if (SQL_STR_DOUBLE(src[i], false)) {
result[j++] = src[i];
}
result[j++] = src[i];
}
result[j] = '\0';
return result;
}
static void AppendSliceItemDDL(StringInfo buf, RangePartitionDefState* sliceDef, CreateStmt* stmt, bool parentheses)
{
Oid typoutput;
bool typIsVarlena = false;
ParseState* pstate = make_parsestate(NULL);
Const* valueConst = NULL;
Node* valueNode = NULL;
const char* valueString = NULL;
char* escapedString = NULL;
ListCell* cell = NULL;
ListCell* cell2 = NULL;
int i = 0;
if (parentheses) {
appendStringInfo(buf, "(");
}
forboth(cell, sliceDef->boundary, cell2, stmt->tableElts) {
if (i > 0) {
appendStringInfo(buf, ", ");
}
valueNode = (Node *)lfirst(cell);
valueConst = (Const*)transformExprRecurse(pstate, valueNode);
if (valueConst->ismaxvalue) {
appendStringInfo(buf, "MAXVALUE");
} else {
getTypeOutputInfo(valueConst->consttype, &typoutput, &typIsVarlena);
valueString = OidOutputFunctionCall(typoutput, valueConst->constvalue);
escapedString = EscapeQuotes(valueString);
appendStringInfo(buf, "'%s'", escapedString);
}
i++;
}
if (parentheses) {
appendStringInfo(buf, ")");
}
pfree_ext(escapedString);
return;
}
static void AppendSliceDN(StringInfo buf, Node* n)
{
char* dnName = NULL;
switch (n->type) {
case T_RangePartitionDefState: {
RangePartitionDefState *sliceDef = (RangePartitionDefState *)n;
dnName = sliceDef->tablespacename;
break;
}
case T_RangePartitionStartEndDefState: {
RangePartitionStartEndDefState *sliceDef = (RangePartitionStartEndDefState *)n;
dnName = sliceDef->tableSpaceName;
break;
}
case T_ListSliceDefState: {
ListSliceDefState *sliceDef = (ListSliceDefState *)n;
dnName = sliceDef->datanode_name;
break;
}
default:
break;
}
if (dnName == NULL) {
return;
}
appendStringInfo(buf, " DATANODE %s", dnName);
}
static void AppendStartEndElement(StringInfo buf, List* valueList, CreateStmt* stmt)
{
Oid typoutput;
bool typIsVarlena = false;
Node* valueNode = NULL;
Const* valueConst = NULL;
const char* valueString = NULL;
ParseState* pstate = NULL;
char* escapedString = NULL;
if (list_length(valueList) != 1) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("distributed table has too many distribution keys."),
errhint("start/end syntax requires a distributed table with only one distribution key.")));
}
pstate = make_parsestate(NULL);
valueNode = (Node *)list_nth(valueList, 0);
valueConst = (Const *)transformExprRecurse(pstate, valueNode);
if (valueConst->ismaxvalue) {
appendStringInfo(buf, "MAXVALUE");
} else {
getTypeOutputInfo(valueConst->consttype, &typoutput, &typIsVarlena);
valueString = OidOutputFunctionCall(typoutput, valueConst->constvalue);
escapedString = EscapeQuotes(valueString);
appendStringInfo(buf, "'%s'", escapedString);
}
pfree_ext(escapedString);
free_parsestate(pstate);
}
static void AppendStartEndDDL(StringInfo buf, RangePartitionStartEndDefState* sliceDef, CreateStmt* stmt)
{
List* startValue = sliceDef->startValue;
List* endValue = sliceDef->endValue;
List* everyValue = sliceDef->everyValue;
appendStringInfo(buf, "SLICE %s ", sliceDef->partitionName);
if (startValue != NIL) {
appendStringInfo(buf, "START(");
AppendStartEndElement(buf, startValue, stmt);
appendStringInfo(buf, ") ");
}
if (endValue != NIL) {
appendStringInfo(buf, "END(");
AppendStartEndElement(buf, endValue, stmt);
appendStringInfo(buf, ") ");
}
if (everyValue != NIL) {
appendStringInfo(buf, "EVERY(");
AppendStartEndElement(buf, everyValue, stmt);
appendStringInfo(buf, ") ");
}
}
static void AppendValuesLessThanDDL(StringInfo buf, Node* node, CreateStmt* stmt)
{
RangePartitionDefState *sliceDef = (RangePartitionDefState *)node;
appendStringInfo(buf, "SLICE %s VALUES LESS THAN (", sliceDef->partitionName);
AppendSliceItemDDL(buf, sliceDef, stmt, false);
appendStringInfo(buf, ")");
AppendSliceDN(buf, node);
pfree(sliceDef);
}
static void AppendListDDL(StringInfo buf, Node* node, CreateStmt* stmt, int keyNum)
{
int listIdx = 0;
ListSliceDefState *listSlice = (ListSliceDefState *)node;
RangePartitionDefState *sliceDef = makeNode(RangePartitionDefState);
ListCell *cell = NULL;
bool parentheses = keyNum > 1;
appendStringInfo(buf, "SLICE %s VALUES (", listSlice->name);
foreach (cell, listSlice->boundaries) {
sliceDef->partitionName = listSlice->name;
sliceDef->boundary = (List *)lfirst(cell);
if (listIdx > 0) {
appendStringInfo(buf, ", ");
}
if (IsDefaultSlice(buf, sliceDef)) {
break;
} else {
AppendSliceItemDDL(buf, sliceDef, stmt, parentheses);
}
listIdx++;
}
appendStringInfo(buf, ")");
AppendSliceDN(buf, node);
pfree(sliceDef);
}
static void AppendSliceDDL(StringInfo buf, CreateStmt* stmt)
{
if (stmt->distributeby->distState == NULL) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Distribution Info is not defined.")));
}
int idx = 0;
DistributeBy* distributeby = stmt->distributeby;
ListCell *cell = NULL;
DistState *distState = distributeby->distState;
int keyNum = list_length(distributeby->colname);
if (distributeby->distState->refTableName != NULL) {
appendStringInfo(buf, " SLICE REFERENCES %s ", distributeby->distState->refTableName);
return;
}
appendStringInfo(buf, "(");
foreach (cell, distState->sliceList) {
Node *node = (Node *)lfirst(cell);
if (idx > 0) {
appendStringInfo(buf, ", ");
}
switch (node->type) {
case T_RangePartitionDefState: {
AppendValuesLessThanDDL(buf, node, stmt);
break;
}
case T_ListSliceDefState: {
AppendListDDL(buf, node, stmt, keyNum);
break;
}
case T_RangePartitionStartEndDefState: {
RangePartitionStartEndDefState* sliceDef = (RangePartitionStartEndDefState *)node;
AppendStartEndDDL(buf, sliceDef, stmt);
break;
}
default: {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("Unrecognized distribution specification.")));
break;
}
}
idx++;
}
appendStringInfo(buf, ")");
}
* get_utility_query_def - Parse back a UTILITY parsetree
* ----------
*/
static void get_utility_query_def(Query* query, deparse_context* context)
{
StringInfo buf = context->buf;
if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) {
NotifyStmt* stmt = (NotifyStmt*)query->utilityStmt;
appendContextKeyword(context, "", 0, PRETTYINDENT_STD, 1);
appendStringInfo(buf, "NOTIFY %s", quote_identifier(stmt->conditionname));
if (stmt->payload) {
appendStringInfoString(buf, ", ");
simple_quote_literal(buf, stmt->payload);
}
}
#ifdef PGXC
else if (query->utilityStmt && IsA(query->utilityStmt, CreateStmt)) {
CreateStmt* stmt = (CreateStmt*)query->utilityStmt;
ListCell* column = NULL;
const char* delimiter = "";
RangeVar* relation = stmt->relation;
bool istemp = (relation->relpersistence == RELPERSISTENCE_TEMP);
bool isunlogged = (relation->relpersistence == RELPERSISTENCE_UNLOGGED);
bool is_matview = stmt->relkind == OBJECT_MATVIEW ? true : false;
if (istemp && relation->schemaname)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("temporary tables cannot specify a schema name")));
appendStringInfo(buf, "CREATE %s %s TABLE ", istemp ? "TEMP" : "", isunlogged ? "UNLOGGED" : "");
if (relation->schemaname && relation->schemaname[0])
appendStringInfo(buf, "%s.", quote_identifier(relation->schemaname));
appendStringInfo(buf, "%s", quote_identifier(relation->relname));
appendStringInfo(buf, "(");
foreach (column, stmt->tableElts) {
Node* node = (Node*)lfirst(column);
appendStringInfo(buf, "%s", delimiter);
delimiter = ", ";
if (IsA(node, ColumnDef)) {
ColumnDef* coldef = (ColumnDef*)node;
TypeName* tpname = coldef->typname;
ClientLogicColumnRef *coldef_enc = coldef->clientLogicColumnRef;
if (!OidIsValid(tpname->typeOid))
ereport(
ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper type oid: \"%u\"", tpname->typeOid)));
if (coldef_enc != NULL && coldef_enc->dest_typname != NULL) {
appendStringInfo(buf,
"%s %s ENCRYPTED WITH (DATATYPE_CL=%s,COLUMN_ENCRYPTION_KEY=%s, ENCRYPTION_TYPE = %s)",
quote_identifier(coldef->colname),
format_type_with_typemod(tpname->typeOid, tpname->typemod),
get_typename_by_id(coldef_enc->dest_typname->typeOid),
NameListToString(coldef_enc->column_key_name),
get_encryption_type_name(coldef_enc->columnEncryptionAlgorithmType));
tpname = coldef_enc->dest_typname;
} else {
appendStringInfo(buf,
"%s %s",
quote_identifier(coldef->colname),
format_type_with_typemod(tpname->typeOid, tpname->typemod));
}
switch (coldef->cmprs_mode) {
case ATT_CMPR_NOCOMPRESS:
appendStringInfoString(buf, " NOCOMPRESS ");
break;
case ATT_CMPR_DELTA:
appendStringInfoString(buf, " DELTA ");
break;
case ATT_CMPR_DICTIONARY:
appendStringInfoString(buf, " DICTIONARY ");
break;
case ATT_CMPR_PREFIX:
appendStringInfoString(buf, " PREFIX ");
break;
case ATT_CMPR_NUMSTR:
appendStringInfoString(buf, " NUMSTR ");
break;
default:
break;
}
} else
ereport(
ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("Invalid table column definition.")));
}
appendStringInfo(buf, ")");
if (list_length(stmt->options) > 0) {
Datum reloptions;
static const char* validnsps[] = HEAP_RELOPT_NAMESPACES;
reloptions = transformRelOptions((Datum)0, stmt->options, NULL, validnsps, false, false);
if (reloptions) {
Datum sep, txt;
sep = CStringGetTextDatum(", ");
txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
appendStringInfo(buf, " WITH (%s)", TextDatumGetCString(txt));
}
}
switch (stmt->oncommit) {
case ONCOMMIT_NOOP:
break;
case ONCOMMIT_PRESERVE_ROWS:
appendStringInfo(buf, " ON COMMIT PRESERVE ROWS");
break;
case ONCOMMIT_DELETE_ROWS:
appendStringInfo(buf, " ON COMMIT DELETE ROWS");
break;
case ONCOMMIT_DROP:
appendStringInfo(buf, " ON COMMIT DROP");
break;
default:
break;
}
switch (stmt->row_compress) {
case REL_CMPRS_NOT_SUPPORT:
case REL_CMPRS_PAGE_PLAIN:
appendStringInfoString(buf, " NOCOMPRESS ");
break;
case REL_CMPRS_FIELDS_EXTRACT:
appendStringInfoString(buf, " COMPRESS ");
break;
case REL_CMPRS_MAX_TYPE:
Assert(false);
break;
default:
break;
}
if (stmt->tablespacename)
appendStringInfo(buf, " TABLESPACE %s ", quote_identifier(stmt->tablespacename));
if (stmt->distributeby) {
ListCell* cell = NULL;
int i = 0;
switch (stmt->distributeby->disttype) {
case DISTTYPE_REPLICATION:
appendStringInfo(buf, " DISTRIBUTE BY REPLICATION");
break;
case DISTTYPE_HASH:
i = 0;
appendStringInfo(buf, " DISTRIBUTE BY HASH(");
foreach (cell, stmt->distributeby->colname) {
if (0 == i) {
appendStringInfo(buf, "%s", quote_identifier(strVal(lfirst(cell))));
} else {
appendStringInfo(buf, " ,%s", quote_identifier(strVal(lfirst(cell))));
}
i++;
}
appendStringInfo(buf, ")");
break;
case DISTTYPE_ROUNDROBIN:
appendStringInfo(buf, " DISTRIBUTE BY ROUNDROBIN");
break;
case DISTTYPE_MODULO:
i = 0;
appendStringInfo(buf, " DISTRIBUTE BY MODULO(");
foreach (cell, stmt->distributeby->colname) {
if (0 == i) {
appendStringInfo(buf, "%s", strVal(lfirst(cell)));
} else {
appendStringInfo(buf, " ,%s", strVal(lfirst(cell)));
}
i++;
}
appendStringInfo(buf, ")");
break;
case DISTTYPE_RANGE: {
appendStringInfo(buf, " DISTRIBUTE BY RANGE");
AppendDistributeByColDef(buf, stmt);
AppendSliceDDL(buf, stmt);
break;
}
case DISTTYPE_LIST: {
appendStringInfo(buf, " DISTRIBUTE BY LIST");
AppendDistributeByColDef(buf, stmt);
AppendSliceDDL(buf, stmt);
break;
}
default:
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Invalid distribution type")));
}
}
if (stmt->subcluster) {
ListCell* cell = NULL;
switch (stmt->subcluster->clustertype) {
case SUBCLUSTER_NODE:
appendStringInfo(buf, " TO NODE (");
Assert(stmt->subcluster->members);
foreach (cell, stmt->subcluster->members) {
appendStringInfo(buf, " %s", strVal(lfirst(cell)));
if (cell->next)
appendStringInfo(buf, ",");
}
appendStringInfo(buf, ")");
break;
case SUBCLUSTER_GROUP:
appendStringInfo(buf, " TO GROUP");
Assert(stmt->subcluster->members);
foreach (cell, stmt->subcluster->members) {
appendStringInfo(buf, " %s", quote_identifier(strVal(lfirst(cell))));
if (cell->next)
appendStringInfo(buf, ",");
}
break;
case SUBCLUSTER_NONE:
default:
break;
}
}
appendStringInfo(buf, " %s ", is_matview ? "FOR MATERIALIZED VIEW" : "");
}else if (query->utilityStmt && IsA(query->utilityStmt, ViewStmt)) {
ViewStmt* stmt = (ViewStmt*)query->utilityStmt;
Query* query = (Query *)stmt->query;
const char* delimiter = "";
RangeVar* relation = stmt->view;
if (stmt->relkind == OBJECT_MATVIEW) {
if (stmt->ivm) {
appendStringInfo(buf, "CREATE INCREMENTAL MATERIALIZED VIEW ");
} else {
appendStringInfo(buf, "CREATE MATERIALIZED VIEW ");
}
} else {
appendStringInfo(buf, "CREATE VIEW ");
}
if (relation->schemaname && relation->schemaname[0])
appendStringInfo(buf, "%s.", quote_identifier(relation->schemaname));
appendStringInfo(buf, "%s", quote_identifier(relation->relname));
appendStringInfo(buf, " ( ");
ListCell* lc = list_head(stmt->aliases);
ListCell* col = NULL;
List* tlist = query->targetList;
foreach (col, tlist) {
TargetEntry* tle = (TargetEntry*)lfirst(col);
if (tle->resjunk)
continue;
appendStringInfo(buf, "%s", delimiter);
delimiter = ", ";
if (lc != NULL) {
appendStringInfo(buf, " %s ", quote_identifier(strVal(lfirst(lc))));
lc = lnext(lc);
} else {
appendStringInfo(buf, " %s ", quote_identifier(tle->resname));
}
}
if (lc != NULL) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE %s specifies too many column names",
stmt->relkind == OBJECT_MATVIEW ? "MATERIALIZED VIEW" : "VIEW AS")));
}
StringInfo select_sql = makeStringInfo();
deparse_query((Query*)query, select_sql, NIL, false, false);
appendStringInfo(buf, " ) AS %s", select_sql->data);
}
#endif
* construct the query tree, if have rewrite rule, get the QueryTree from system table pg_rewrite
* and then convert it to original string
*/
else if (query->utilityStmt && IsA(query->utilityStmt, CopyStmt)) {
CopyStmt* stmt = (CopyStmt*)query->utilityStmt;
RangeVar* relation = stmt->relation;
appendStringInfo(buf, "COPY ");
if (relation->schemaname && relation->schemaname[0]) {
appendStringInfo(buf, "%s.", quote_identifier(relation->schemaname));
}
appendStringInfo(buf, "%s", quote_identifier(relation->relname));
if (stmt->is_from) {
appendStringInfo(buf, " FROM ");
} else {
appendStringInfo(buf, " TO ");
}
if (stmt->filename) {
appendStringInfo(buf, "\'%s\'", stmt->filename);
} else {
* ignore the STDIN and STDOUT, redisanyvalue is same as stdin,
* if should get the true sql, need to support
*/
appendStringInfo(buf, "REDISANYVALUE");
}
} else if (query->utilityStmt && IsA(query->utilityStmt, AlterTableStmt)) {
AlterTableStmt* stmt = (AlterTableStmt*)query->utilityStmt;
get_utility_stmt_def(stmt, buf);
} else {
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unexpected utility statement type")));
}
}
#ifdef PGXC
static void getVariableFromRTERemoteDummy(
Var* var, deparse_namespace* dpns, deparse_context* context, int netlevelsup, bool no_alias)
{
TargetEntry* tle = NULL;
RemoteQuery* rqplan = NULL;
Assert(netlevelsup == 0);
StringInfo buf = context->buf;
* Get the expression representing the given Var from base_tlist of the RemoteQuery.
* If the planstate is ModifyTableState, use ModifyTableState's mt_remoterels due
* to the varno has been changed by ModifyTable's remote_plan in set_plan_reference.
*/
if (IsA(dpns->planstate, RemoteQueryState)) {
rqplan = castNode(RemoteQuery, dpns->planstate->plan);
} else if (IsA(dpns->planstate, ModifyTableState)) {
ModifyTableState *planstate = castNode(ModifyTableState, dpns->planstate);
Assert(planstate->mt_remoterels != NULL);
rqplan = castNode(RemoteQuery, planstate->mt_remoterels[0]->plan);
}
if (rqplan != NULL) {
tle = get_tle_by_resno(rqplan->base_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for remotequery var: %d", var->varattno)));
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)tle->expr, context, true, no_alias);
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
} else {
appendStringInfoChar(buf, '?');
}
return;
}
#endif
* Display a Var appropriately.
*
* In some cases (currently only when recursing into an unnamed join)
* the Var's varlevelsup has to be interpreted with respect to a context
* above the current one; levelsup indicates the offset.
*
* If istoplevel is TRUE, the Var is at the top level of a SELECT's
* targetlist, which means we need special treatment of whole-row Vars.
* Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
* dirty hack to prevent "tab.*" from being expanded into multiple columns.
* (The parser will strip the useless coercion, so no inefficiency is added in
* dump and reload.) We used to print just "tab" in such cases, but that is
* ambiguous and will yield the wrong result if "tab" is also a plain column
* name in the query.
*
* Returns the attname of the Var, or NULL if the Var has no attname (because
* it is a whole-row Var).
*/
static char* get_variable(
Var* var, int levelsup, bool istoplevel, deparse_context* context, bool for_star, bool no_alias)
{
StringInfo buf = context->buf;
RangeTblEntry* rte = NULL;
AttrNumber attnum = InvalidAttrNumber;
int netlevelsup;
deparse_namespace* dpns = NULL;
char* schemaname = NULL;
char* refname = NULL;
char* attname = NULL;
netlevelsup = var->varlevelsup + levelsup;
if (netlevelsup >= list_length(context->namespaces)) {
if (context->qrw_phase) {
appendStringInfo(buf, "Param(%d,%d)", var->varlevelsup, levelsup);
return NULL;
} else
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varlevelsup: %d offset %d", var->varlevelsup, levelsup)));
}
dpns = (deparse_namespace*)list_nth(context->namespaces, netlevelsup);
* Try to find the relevant RTE in this rtable. In a plan tree, it's
* likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
* down into the subplans, or INDEX_VAR, which is resolved similarly.
*/
#ifdef PGXC
if (dpns->remotequery) {
rte = rt_fetch(1, dpns->rtable);
attnum = var->varattno;
} else
#endif
if (var->varno >= 1 && var->varno <= (uint)list_length(dpns->rtable)) {
rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno;
} else if ((var->varno == OUTER_VAR) && dpns->outer_tlist) {
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for OUTER_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)tle->expr, context, true, no_alias);
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
pop_child_plan(dpns, &save_dpns);
return NULL;
} else if (var->varno == INNER_VAR && dpns->inner_tlist) {
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for INNER_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
bool push = dpns->inner_planstate != NULL;
if (push) {
push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
}
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)tle->expr, context, true, no_alias);
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
if (push) {
pop_child_plan(dpns, &save_dpns);
}
return NULL;
} else if (var->varno == INDEX_VAR && dpns->index_tlist) {
TargetEntry* tle = NULL;
tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for INDEX_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)tle->expr, context, true, no_alias);
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
return NULL;
} else {
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("bogus varno: %d", var->varno)));
return NULL;
}
* The planner will sometimes emit Vars referencing resjunk elements of a
* subquery's target list (this is currently only possible if it chooses
* to generate a "physical tlist" for a SubqueryScan or CteScan node).
* Although we prefer to print subquery-referencing Vars using the
* subquery's alias, that's not possible for resjunk items since they have
* no alias. So in that case, drill down to the subplan and print the
* contents of the referenced tlist item. This works because in a plan
* tree, such Vars can only occur in a SubqueryScan or CteScan node, and
* we'll have set dpns->inner_planstate to reference the child plan node.
*/
if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) && attnum > list_length(rte->eref->colnames) &&
dpns->inner_planstate) {
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("bogus varattno for subquery var: %d", var->varattno)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)tle->expr, context, true, no_alias);
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
pop_child_plan(dpns, &save_dpns);
return NULL;
}
#ifdef PGXC
if (rte->rtekind == RTE_REMOTE_DUMMY && attnum > list_length(rte->eref->colnames) && dpns->planstate) {
getVariableFromRTERemoteDummy(var, dpns, context, netlevelsup, no_alias);
return NULL;
}
#endif
schemaname = NULL;
if (NULL != rte->relname && u_sess->hook_cxt.forTsdbHook) {
char *relname = get_rel_name(rte->relid);
if (relname != NULL) {
rte->relname = pstrdup(relname);
rte->eref->aliasname = pstrdup(rte->relname);
}
}
refname = rte->eref->aliasname;
if (rte->alias == NULL) {
if (rte->rtekind == RTE_RELATION || no_alias) {
* It's possible that use of the bare refname would find another
* more-closely-nested RTE, or be ambiguous, in which case we need
* to specify the schemaname to avoid these errors.
*/
if (find_rte_by_refname(rte->eref->aliasname, context) != rte || no_alias)
schemaname = get_namespace_name(get_rel_namespace(rte->relid));
#ifndef ENABLE_MULTIPLE_NODES
if (no_alias)
refname = rte->relname;
#endif
} else if (rte->rtekind == RTE_JOIN) {
* If it's an unnamed join, look at the expansion of the alias
* variable. If it's a simple reference to one of the input vars
* then recursively print the name of that var, instead. (This
* allows correct decompiling of cases where there are identically
* named columns on both sides of the join.) When it's not a
* simple reference, we have to just print the unqualified
* variable name (this can only happen with columns that were
* merged by USING or NATURAL clauses).
*
* This wouldn't work in decompiling plan trees, because we don't
* store joinaliasvars lists after planning; but a plan tree
* should never contain a join alias variable.
*/
if (rte->joinaliasvars == NIL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("cannot decompile join alias var in plan tree")));
if (attnum > 0) {
Var* aliasvar = NULL;
aliasvar = (Var*)list_nth(rte->joinaliasvars, attnum - 1);
if (IsA(aliasvar, Var)) {
return get_variable(
aliasvar, var->varlevelsup + levelsup, istoplevel, context, for_star, no_alias);
}
}
* Unnamed join has neither schemaname nor refname. (Note: since
* it's unnamed, there is no way the user could have referenced it
* to create a whole-row Var for it. So we don't have to cover
* that case below.)
*/
refname = NULL;
}
}
if (attnum == InvalidAttrNumber)
attname = NULL;
else
attname = get_rte_attribute_name(rte, attnum, true);
if (refname && (context->varprefix || attname == NULL)) {
if (schemaname != NULL)
appendStringInfo(buf, "%s.", quote_identifier(schemaname));
appendStringInfoString(buf, quote_identifier(refname));
appendStringInfoChar(buf, '.');
}
if (for_star)
appendStringInfoChar(buf, '*');
else if (attname != NULL)
appendStringInfoString(buf, quote_identifier(attname));
else {
appendStringInfoChar(buf, '*');
if (istoplevel)
appendStringInfo(buf, "::%s", format_type_with_typemod(var->vartype, var->vartypmod));
}
return attname;
}
* Get the name of a field of an expression of composite type. The
* expression is usually a Var, but we handle other cases too.
*
* levelsup is an extra offset to interpret the Var's varlevelsup correctly.
*
* This is fairly straightforward when the expression has a named composite
* type; we need only look up the type in the catalogs. However, the type
* could also be RECORD. Since no actual table or view column is allowed to
* have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE
* or to a subquery output. We drill down to find the ultimate defining
* expression and attempt to infer the field name from it. We ereport if we
* can't determine the name.
*
* Similarly, a PARAM of type RECORD has to refer to some expression of
* a determinable composite type.
*/
static const char* get_name_for_var_field(Var* var, int fieldno, int levelsup, deparse_context* context)
{
RangeTblEntry* rte = NULL;
AttrNumber attnum = InvalidAttrNumber;
int netlevelsup;
deparse_namespace* dpns = NULL;
TupleDesc tupleDesc;
Node* expr = NULL;
* If it's a RowExpr that was expanded from a whole-row Var, use the
* column names attached to it.
*/
if (IsA(var, RowExpr)) {
RowExpr* r = (RowExpr*)var;
if (fieldno > 0 && fieldno <= list_length(r->colnames))
return strVal(list_nth(r->colnames, fieldno - 1));
}
* If it's a Param of type RECORD, try to find what the Param refers to.
*/
if (IsA(var, Param)) {
Param* param = (Param*)var;
ListCell* ancestor_cell = NULL;
expr = find_param_referent(param, context, &dpns, &ancestor_cell);
if (expr != NULL) {
deparse_namespace save_dpns;
const char* result = NULL;
push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
result = get_name_for_var_field((Var*)expr, fieldno, 0, context);
pop_ancestor_plan(dpns, &save_dpns);
return result;
}
}
* If it's a Var of type RECORD, we have to find what the Var refers to;
* if not, we can use get_expr_result_type. If that fails, we try
* lookup_rowtype_tupdesc, which will probably fail too, but will ereport
* an acceptable message.
*/
if (!IsA(var, Var) || var->vartype != RECORDOID) {
if (get_expr_result_type((Node*)var, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
tupleDesc = lookup_rowtype_tupdesc_copy(exprType((Node*)var), exprTypmod((Node*)var));
Assert(tupleDesc);
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(tupleDesc->attrs[fieldno - 1].attname);
}
netlevelsup = var->varlevelsup + levelsup;
if (netlevelsup >= list_length(context->namespaces))
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("bogus varattno for remotequery var: %d", netlevelsup)));
dpns = (deparse_namespace*)list_nth(context->namespaces, netlevelsup);
* Try to find the relevant RTE in this rtable. In a plan tree, it's
* likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
* down into the subplans, or INDEX_VAR, which is resolved similarly.
*/
if (var->varno >= 1 && var->varno <= (uint)list_length(dpns->rtable)) {
rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno;
} else if (var->varno == OUTER_VAR && dpns->outer_tlist) {
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
const char* result = NULL;
tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for OUTER_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
result = get_name_for_var_field((Var*)tle->expr, fieldno, levelsup, context);
pop_child_plan(dpns, &save_dpns);
return result;
} else if (var->varno == INNER_VAR && dpns->inner_tlist) {
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
const char* result = NULL;
tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for INNER_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var*)tle->expr, fieldno, levelsup, context);
pop_child_plan(dpns, &save_dpns);
return result;
} else if (var->varno == INDEX_VAR && dpns->index_tlist) {
TargetEntry* tle = NULL;
const char* result = NULL;
tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for INDEX_VAR var: %d", var->varattno)));
Assert(netlevelsup == 0);
result = get_name_for_var_field((Var*)tle->expr, fieldno, levelsup, context);
return result;
} else {
ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("bogus varno: %d", var->varno)));
return NULL;
}
if (attnum == InvalidAttrNumber) {
return get_rte_attribute_name(rte, fieldno);
}
* This part has essentially the same logic as the parser's
* expandRecordVariable() function, but we are dealing with a different
* representation of the input context, and we only need one field name
* not a TupleDesc. Also, we need special cases for finding subquery and
* CTE subplans when deparsing Plan trees.
*/
expr = (Node*)var;
switch (rte->rtekind) {
case RTE_RELATION:
case RTE_VALUES:
case RTE_RESULT:
* This case should not occur: a column of a table or values list
* shouldn't have type RECORD. Fall through and fail (most
* likely) at the bottom.
*/
break;
case RTE_SUBQUERY:
{
if (rte->subquery) {
TargetEntry* ste = get_tle_by_resno(rte->subquery->targetList, attnum);
if (ste == NULL || ste->resjunk)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("subquery %s does not have attribute %d", rte->eref->aliasname, attnum)));
expr = (Node*)ste->expr;
if (IsA(expr, Var)) {
* Recurse into the sub-select to see what its Var
* refers to. We have to build an additional level of
* namespace to keep in step with varlevelsup in the
* subselect.
*/
deparse_namespace mydpns;
const char* result = NULL;
errno_t rc = memset_s(&mydpns, sizeof(mydpns), 0, sizeof(mydpns));
securec_check(rc, "\0", "\0");
mydpns.rtable = rte->subquery->rtable;
mydpns.ctes = rte->subquery->cteList;
#ifdef PGXC
mydpns.remotequery = false;
#endif
context->namespaces = lcons(&mydpns, context->namespaces);
result = get_name_for_var_field((Var*)expr, fieldno, 0, context);
context->namespaces = list_delete_first(context->namespaces);
return result;
}
} else {
* We're deparsing a Plan tree so we don't have complete
* RTE entries (in particular, rte->subquery is NULL). But
* the only place we'd see a Var directly referencing a
* SUBQUERY RTE is in a SubqueryScan plan node, and we can
* look into the child plan's tlist instead.
*/
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
const char* result = NULL;
if (dpns->inner_planstate == NULL)
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("failed to find plan for subquery %s", rte->eref->aliasname))));
tle = get_tle_by_resno(dpns->inner_tlist, attnum);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for subquery var: %d", attnum)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var*)tle->expr, fieldno, levelsup, context);
pop_child_plan(dpns, &save_dpns);
return result;
}
}
break;
case RTE_JOIN:
if (rte->joinaliasvars == NIL)
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("cannot decompile join alias var in plan tree"))));
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
expr = (Node*)list_nth(rte->joinaliasvars, attnum - 1);
if (IsA(expr, Var))
return get_name_for_var_field((Var*)expr, fieldno, var->varlevelsup + levelsup, context);
break;
case RTE_FUNCTION:
* We couldn't get here unless a function is declared with one of
* its result columns as RECORD, which is not allowed.
*/
break;
case RTE_CTE:
{
CommonTableExpr* cte = NULL;
Index ctelevelsup;
ListCell* lc = NULL;
* Try to find the referenced CTE using the namespace stack.
*/
ctelevelsup = rte->ctelevelsup + netlevelsup;
if (ctelevelsup >= (uint)list_length(context->namespaces))
lc = NULL;
else {
deparse_namespace* ctedpns = NULL;
ctedpns = (deparse_namespace*)list_nth(context->namespaces, ctelevelsup);
foreach (lc, ctedpns->ctes) {
cte = (CommonTableExpr*)lfirst(lc);
if (strcmp(cte->ctename, rte->ctename) == 0)
break;
}
}
if (lc != NULL) {
Query* ctequery = (Query*)cte->ctequery;
TargetEntry* ste = get_tle_by_resno(GetCTETargetList(cte), attnum);
if (ste == NULL || ste->resjunk)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("subquery %s does not have attribute %d", rte->eref->aliasname, attnum)));
expr = (Node*)ste->expr;
if (IsA(expr, Var)) {
* Recurse into the CTE to see what its Var refers to.
* We have to build an additional level of namespace
* to keep in step with varlevelsup in the CTE.
* Furthermore it could be an outer CTE, so we may
* have to delete some levels of namespace.
*/
List* save_nslist = context->namespaces;
List* new_nslist = NIL;
deparse_namespace mydpns;
const char* result = NULL;
errno_t rc = memset_s(&mydpns, sizeof(mydpns), 0, sizeof(mydpns));
securec_check(rc, "\0", "\0");
mydpns.rtable = ctequery->rtable;
mydpns.ctes = ctequery->cteList;
#ifdef PGXC
mydpns.remotequery = false;
#endif
new_nslist = list_copy_tail(context->namespaces, ctelevelsup);
context->namespaces = lcons(&mydpns, new_nslist);
result = get_name_for_var_field((Var*)expr, fieldno, 0, context);
context->namespaces = save_nslist;
return result;
}
} else {
* We're deparsing a Plan tree so we don't have a CTE
* list. But the only place we'd see a Var directly
* referencing a CTE RTE is in a CteScan plan node, and we
* can look into the subplan's tlist instead.
*/
TargetEntry* tle = NULL;
deparse_namespace save_dpns;
const char* result = NULL;
if (dpns->inner_planstate == NULL)
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_UNEXPECTED_NODE_STATE),
errmsg("failed to find plan for CTE %s", rte->eref->aliasname))));
tle = get_tle_by_resno(dpns->inner_tlist, attnum);
if (tle == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
errmsg("bogus varattno for subquery var: %d", attnum)));
Assert(netlevelsup == 0);
push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var*)tle->expr, fieldno, levelsup, context);
pop_child_plan(dpns, &save_dpns);
return result;
}
}
break;
#ifdef PGXC
case RTE_REMOTE_DUMMY:
ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("Invalid RTE found")));
break;
#endif
default:
break;
}
* We now have an expression we can't expand any more, so see if
* get_expr_result_type() can do anything with it. If not, pass to
* lookup_rowtype_tupdesc() which will probably fail, but will give an
* appropriate error message while failing.
*/
if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr), exprTypmod(expr));
Assert(tupleDesc);
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(tupleDesc->attrs[fieldno - 1].attname);
}
* find_rte_by_refname - look up an RTE by refname in a deparse context
*
* Returns NULL if there is no matching RTE or the refname is ambiguous.
*
* NOTE: this code is not really correct since it does not take account of
* the fact that not all the RTEs in a rangetable may be visible from the
* point where a Var reference appears. For the purposes we need, however,
* the only consequence of a false match is that we might stick a schema
* qualifier on a Var that doesn't really need it. So it seems close
* enough.
*/
static RangeTblEntry* find_rte_by_refname(const char* refname, deparse_context* context)
{
RangeTblEntry* result = NULL;
ListCell* nslist = NULL;
foreach (nslist, context->namespaces) {
deparse_namespace* dpns = (deparse_namespace*)lfirst(nslist);
ListCell* rtlist = NULL;
foreach (rtlist, dpns->rtable) {
RangeTblEntry* rte = (RangeTblEntry*)lfirst(rtlist);
if (!(context->is_upsert_clause && rte->pulled_from_subquery) &&
strcmp(rte->eref->aliasname, refname) == 0) {
if (result != NULL)
return NULL;
result = rte;
}
}
if (result != NULL)
break;
}
return result;
}
* Try to find the referenced expression for a PARAM_EXEC Param that might
* reference a parameter supplied by an upper NestLoop or SubPlan plan node.
*
* If successful, return the expression and set *dpns_p and *ancestor_cell_p
* appropriately for calling push_ancestor_plan(). If no referent can be
* found, return NULL.
*/
static Node* find_param_referent(
Param* param, deparse_context* context, deparse_namespace** dpns_p, ListCell** ancestor_cell_p)
{
*dpns_p = NULL;
*ancestor_cell_p = NULL;
* If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or
* SubPlan argument. This will necessarily be in some ancestor of the
* current expression's PlanState.
*/
if (param->paramkind == PARAM_EXEC) {
deparse_namespace* dpns = NULL;
PlanState* child_ps = NULL;
bool in_same_plan_level = false;
ListCell* lc = NULL;
dpns = (deparse_namespace*)linitial(context->namespaces);
child_ps = dpns->planstate;
in_same_plan_level = true;
foreach (lc, dpns->ancestors) {
PlanState* ps = (PlanState*)lfirst(lc);
ListCell* lc2 = NULL;
* NestLoops transmit params to their inner child only; also, once
* we've crawled up out of a subplan, this couldn't possibly be
* the right match.
*/
if (IsA(ps, NestLoopState) && child_ps == innerPlanState(ps) && in_same_plan_level) {
NestLoop* nl = (NestLoop*)ps->plan;
foreach (lc2, nl->nestParams) {
NestLoopParam* nlp = (NestLoopParam*)lfirst(lc2);
if (nlp->paramno == param->paramid) {
*dpns_p = dpns;
*ancestor_cell_p = lc;
return (Node*)nlp->paramval;
}
}
}
* Check to see if we're crawling up from a subplan.
*/
foreach (lc2, ps->subPlan) {
SubPlanState* sstate = (SubPlanState*)lfirst(lc2);
SubPlan* subplan = (SubPlan*)sstate->xprstate.expr;
ListCell* lc3 = NULL;
ListCell* lc4 = NULL;
if (child_ps != sstate->planstate)
continue;
forboth(lc3, subplan->parParam, lc4, subplan->args)
{
int paramid = lfirst_int(lc3);
Node* arg = (Node*)lfirst(lc4);
if (paramid == param->paramid) {
*dpns_p = dpns;
*ancestor_cell_p = lc;
return arg;
}
}
in_same_plan_level = false;
break;
}
* Likewise check to see if we're emerging from an initplan.
* Initplans never have any parParams, so no need to search that
* list, but we need to know if we should reset
* in_same_plan_level.
*/
foreach (lc2, ps->initPlan) {
SubPlanState* sstate = (SubPlanState*)lfirst(lc2);
if (child_ps != sstate->planstate)
continue;
Assert(((SubPlan*)sstate->xprstate.expr)->parParam == NIL);
in_same_plan_level = false;
break;
}
child_ps = ps;
}
}
return NULL;
}
* Display a Param appropriately.
*/
static void get_parameter(Param* param, deparse_context* context)
{
Node* expr = NULL;
deparse_namespace* dpns = NULL;
ListCell* ancestor_cell = NULL;
* If it's a PARAM_EXEC parameter, try to locate the expression from which
* the parameter was computed. Note that failing to find a referent isn't
* an error, since the Param might well be a subplan output rather than an
* input.
*/
expr = find_param_referent(param, context, &dpns, &ancestor_cell);
if (expr != NULL) {
deparse_namespace save_dpns;
bool save_varprefix = false;
bool need_paren = false;
push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
* Force prefixing of Vars, since they won't belong to the relation
* being scanned in the original plan node.
*/
save_varprefix = context->varprefix;
context->varprefix = true;
* A Param's expansion is typically a Var, Aggref, or upper-level
* Param, which wouldn't need extra parentheses. Otherwise, insert
* parens to ensure the expression looks atomic.
*/
need_paren = !(IsA(expr, Var) || IsA(expr, Aggref) || IsA(expr, Param));
if (need_paren)
appendStringInfoChar(context->buf, '(');
get_rule_expr(expr, context, false);
if (need_paren)
appendStringInfoChar(context->buf, ')');
context->varprefix = save_varprefix;
pop_ancestor_plan(dpns, &save_dpns);
return;
}
* If it's a PARAM_EXTERN parameter (only occurs while deparse of create table as stmt),
* try to locate the expression from dynamic variables from plsql context
*/
else if (param->paramkind == PARAM_EXTERN && context->parser_arg != NULL) {
PLpgSQL_expr* arg = (PLpgSQL_expr*)context->parser_arg;
struct PLpgSQL_nsitem* nsitem = NULL;
for (nsitem = arg->ns; nsitem->itemtype == PLPGSQL_NSTYPE_VAR; nsitem = nsitem->prev) {
if (nsitem->itemno == param->paramid - 1) {
appendStringInfo(context->buf, "%s", nsitem->name);
return;
}
}
}
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
appendStringInfo(context->buf, "$%d", param->paramid);
}
* get_simple_binary_op_name
*
* helper function for isSimpleNode
* will return single char binary operator name, or NULL if it's not
*/
static const char* get_simple_binary_op_name(OpExpr* expr)
{
List* args = expr->args;
if (list_length(args) == 2) {
Node* arg1 = (Node*)linitial(args);
Node* arg2 = (Node*)lsecond(args);
const char* op = NULL;
op = generate_operator_name(expr->opno, exprType(arg1), exprType(arg2));
if (strlen(op) == 1)
return op;
}
return NULL;
}
* isSimpleNode - check if given node is simple (doesn't need parenthesizing)
*
* true : simple in the context of parent node's type
* false : not simple
*/
static bool isSimpleNode(Node* node, Node* parentNode, int prettyFlags)
{
if (node == NULL)
return false;
switch (nodeTag(node)) {
case T_Var:
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
case T_SetToDefault:
case T_CurrentOfExpr:
return true;
case T_ArrayRef:
case T_ArrayExpr:
case T_RowExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_XmlExpr:
case T_NullIfExpr:
case T_Aggref:
case T_WindowFunc:
case T_FuncExpr:
return true;
case T_CaseExpr:
return true;
case T_FieldSelect:
* appears simple since . has top precedence, unless parent is
* T_FieldSelect itself!
*/
return (IsA(parentNode, FieldSelect) ? false : true);
case T_FieldStore:
* treat like FieldSelect (probably doesn't matter)
*/
return (IsA(parentNode, FieldStore) ? false : true);
case T_CoerceToDomain:
return isSimpleNode((Node*)((CoerceToDomain*)node)->arg, node, prettyFlags);
case T_RelabelType:
return isSimpleNode((Node*)((RelabelType*)node)->arg, node, prettyFlags);
case T_CoerceViaIO:
return isSimpleNode((Node*)((CoerceViaIO*)node)->arg, node, prettyFlags);
case T_ArrayCoerceExpr:
return isSimpleNode((Node*)((ArrayCoerceExpr*)node)->arg, node, prettyFlags);
case T_ConvertRowtypeExpr:
return isSimpleNode((Node*)((ConvertRowtypeExpr*)node)->arg, node, prettyFlags);
case T_OpExpr: {
if ((unsigned int)prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr)) {
const char* op = NULL;
const char* parentOp = NULL;
bool is_lopriop = false;
bool is_hipriop = false;
bool is_lopriparent = false;
bool is_hipriparent = false;
op = get_simple_binary_op_name((OpExpr*)node);
if (op == NULL)
return false;
is_lopriop = (strchr("+-", *op) != NULL);
is_hipriop = (strchr("*/%", *op) != NULL);
if (!(is_lopriop || is_hipriop))
return false;
parentOp = get_simple_binary_op_name((OpExpr*)parentNode);
if (parentOp == NULL)
return false;
is_lopriparent = (strchr("+-", *parentOp) != NULL);
is_hipriparent = (strchr("*/%", *parentOp) != NULL);
if (!(is_lopriparent || is_hipriparent))
return false;
if (is_hipriop && is_lopriparent)
return true;
if (is_lopriop && is_hipriparent)
return false;
* Operators are same priority --- can skip parens only if
* we have (a - b) - c, not a - (b - c).
*/
if (node == (Node*)linitial(((OpExpr*)parentNode)->args))
return true;
return false;
}
}
case T_SubLink:
case T_NullTest:
case T_NanTest:
case T_InfiniteTest:
case T_BooleanTest:
case T_HashFilter:
case T_DistinctExpr:
switch (nodeTag(parentNode)) {
case T_FuncExpr: {
CoercionForm type = ((FuncExpr*)parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST || type == COERCE_IMPLICIT_CAST)
return false;
return true;
}
case T_BoolExpr:
case T_ArrayRef:
case T_ArrayExpr:
case T_RowExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_XmlExpr:
case T_NullIfExpr:
case T_Aggref:
case T_WindowFunc:
case T_CaseExpr:
return true;
default:
return false;
}
case T_BoolExpr:
switch (nodeTag(parentNode)) {
case T_BoolExpr:
if (prettyFlags & PRETTYFLAG_PAREN) {
BoolExprType type;
BoolExprType parentType;
type = ((BoolExpr*)node)->boolop;
parentType = ((BoolExpr*)parentNode)->boolop;
switch (type) {
case NOT_EXPR:
case AND_EXPR:
if (parentType == AND_EXPR || parentType == OR_EXPR)
return true;
break;
case OR_EXPR:
if (parentType == OR_EXPR)
return true;
break;
default:
break;
}
}
return false;
case T_FuncExpr: {
CoercionForm type = ((FuncExpr*)parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST || type == COERCE_IMPLICIT_CAST)
return false;
return true;
}
case T_ArrayRef:
case T_ArrayExpr:
case T_RowExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_XmlExpr:
case T_NullIfExpr:
case T_Aggref:
case T_WindowFunc:
case T_CaseExpr:
return true;
default:
return false;
}
default:
break;
}
return false;
}
* appendContextKeyword - append a keyword to buffer
*
* If prettyPrint is enabled, perform a line break, and adjust indentation.
* Otherwise, just append the keyword.
*/
static void appendContextKeyword(
deparse_context* context, const char* str, int indentBefore, int indentAfter, int indentPlus)
{
if (PRETTY_INDENT(context)) {
context->indentLevel += indentBefore;
appendStringInfoChar(context->buf, '\n');
appendStringInfoSpaces(context->buf, Max(context->indentLevel, 0) + indentPlus);
appendStringInfoString(context->buf, str);
context->indentLevel += indentAfter;
if (context->indentLevel < 0)
context->indentLevel = 0;
} else
appendStringInfoString(context->buf, str);
}
* get_rule_expr_paren - deparse expr using get_rule_expr,
* embracing the string with parentheses if necessary for prettyPrint.
*
* Never embrace if prettyFlags=0, because it's done in the calling node.
*
* Any node that does *not* embrace its argument node by sql syntax (with
* parentheses, non-operator keywords like CASE/WHEN/ON, or comma etc) should
* use get_rule_expr_paren instead of get_rule_expr so parentheses can be
* added.
*/
static void get_rule_expr_paren(
Node* node, deparse_context* context, bool showimplicit, Node* parentNode, bool no_alias = false)
{
bool need_paren = false;
need_paren = PRETTY_PAREN(context) && !isSimpleNode(node, parentNode, context->prettyFlags);
if (need_paren)
appendStringInfoChar(context->buf, '(');
get_rule_expr(node, context, showimplicit, no_alias);
if (need_paren)
appendStringInfoChar(context->buf, ')');
}
* get_rule_expr - Parse back an expression
*
* Note: showimplicit determines whether we display any implicit cast that
* is present at the top of the expression tree. It is a passed argument,
* not a field of the context struct, because we change the value as we
* recurse down into the expression. In general we suppress implicit casts
* when the result type is known with certainty (eg, the arguments of an
* OR must be boolean). We display implicit casts for arguments of functions
* and operators, since this is needed to be certain that the same function
* or operator will be chosen when the expression is re-parsed.
* ----------
*/
static void get_rule_expr(Node* node, deparse_context* context, bool showimplicit, bool no_alias)
{
StringInfo buf = context->buf;
char* tmp = NULL;
if (node == NULL)
return;
* Each level of get_rule_expr must emit an indivisible term
* (parenthesized if necessary) to ensure result is reparsed into the same
* expression tree. The only exception is that when the input is a List,
* we emit the component items comma-separated with no surrounding
* decoration; this is convenient for most callers.
*/
switch (nodeTag(node)) {
case T_Var:
tmp = get_variable((Var*)node, 0, false, context, false, no_alias);
if (tmp != NULL) {
pfree_ext(tmp);
}
break;
case T_Rownum:
appendStringInfo(buf, "ROWNUM");
break;
case T_Const:
get_const_expr((Const*)node, context, 0);
break;
case T_UserVar:
appendStringInfo(buf, "@");
appendStringInfo(buf, "%s", ((UserVar*)node)->name);
break;
case T_UserSetElem:{
UserSetElem* userSet = (UserSetElem*)node;
appendStringInfoString(buf, "@var := ");
get_rule_expr((Node*)userSet->val, context, true, no_alias);
break;
}
case T_SetVariableExpr:
get_const_expr((Const*)(((SetVariableExpr*)node)->value), context, 0);
break;
case T_Param:
get_parameter((Param*)node, context);
break;
case T_Aggref:
get_agg_expr((Aggref*)node, context);
break;
case T_GroupingFunc: {
GroupingFunc* gexpr = (GroupingFunc*)node;
appendStringInfoString(buf, "GROUPING(");
get_rule_expr((Node*)gexpr->args, context, true, no_alias);
appendStringInfoChar(buf, ')');
} break;
case T_GroupingId: {
appendStringInfoString(buf, "GROUPINGID()");
} break;
case T_WindowFunc:
get_windowfunc_expr((WindowFunc*)node, context);
break;
case T_ArrayRef: {
ArrayRef* aref = (ArrayRef*)node;
bool need_parens = false;
* If the argument is a CaseTestExpr, we must be inside a
* FieldStore, ie, we are assigning to an element of an array
* within a composite column. Since we already punted on
* displaying the FieldStore's target information, just punt
* here too, and display only the assignment source
* expression.
*/
if (IsA(aref->refexpr, CaseTestExpr)) {
Assert(aref->refassgnexpr);
get_rule_expr((Node*)aref->refassgnexpr, context, showimplicit, no_alias);
break;
}
* Parenthesize the argument unless it's a simple Var or a
* FieldSelect. (In particular, if it's another ArrayRef, we
* *must* parenthesize to avoid confusion.)
*/
need_parens = !IsA(aref->refexpr, Var) && !IsA(aref->refexpr, FieldSelect);
if (need_parens)
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)aref->refexpr, context, showimplicit, no_alias);
if (need_parens)
appendStringInfoChar(buf, ')');
* If there's a refassgnexpr, we want to print the node in the
* format "array[subscripts] := refassgnexpr". This is not
* legal SQL, so decompilation of INSERT or UPDATE statements
* should always use processIndirection as part of the
* statement-level syntax. We should only see this when
* EXPLAIN tries to print the targetlist of a plan resulting
* from such a statement.
*/
if (aref->refassgnexpr) {
Node* refassgnexpr = NULL;
* Use processIndirection to print this node's subscripts
* as well as any additional field selections or
* subscripting in immediate descendants. It returns the
* RHS expr that is actually being "assigned".
*/
refassgnexpr = processIndirection(node, context, true);
appendStringInfoString(buf, " := ");
get_rule_expr(refassgnexpr, context, showimplicit, no_alias);
} else {
printSubscripts(aref, context);
}
} break;
case T_FuncExpr:
get_func_expr((FuncExpr*)node, context, showimplicit);
break;
case T_NamedArgExpr: {
NamedArgExpr* na = (NamedArgExpr*)node;
appendStringInfo(buf, "%s := ", quote_identifier(na->name));
get_rule_expr((Node*)na->arg, context, showimplicit, no_alias);
} break;
case T_OpExpr:
get_oper_expr((OpExpr*)node, context, no_alias);
break;
case T_DistinctExpr: {
DistinctExpr* expr = (DistinctExpr*)node;
List* args = expr->args;
Node* arg1 = (Node*)linitial(args);
Node* arg2 = (Node*)lsecond(args);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg1, context, true, node, no_alias);
appendStringInfo(buf, " IS DISTINCT FROM ");
get_rule_expr_paren(arg2, context, true, node, no_alias);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_NullIfExpr: {
NullIfExpr* nullifexpr = (NullIfExpr*)node;
appendStringInfo(buf, "NULLIF(");
get_rule_expr((Node*)nullifexpr->args, context, true, no_alias);
appendStringInfoChar(buf, ')');
} break;
case T_ScalarArrayOpExpr: {
ScalarArrayOpExpr* expr = (ScalarArrayOpExpr*)node;
List* args = expr->args;
Node* arg1 = (Node*)linitial(args);
Node* arg2 = (Node*)lsecond(args);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg1, context, true, node, no_alias);
appendStringInfo(buf,
" %s %s (",
generate_operator_name(expr->opno, exprType(arg1), get_base_element_type(exprType(arg2))),
expr->useOr ? "ANY" : "ALL");
get_rule_expr_paren(arg2, context, true, node, no_alias);
* There's inherent ambiguity in "x op ANY/ALL (y)" when y is
* a bare sub-SELECT. Since we're here, the sub-SELECT must
* be meant as a scalar sub-SELECT yielding an array value to
* be used in ScalarArrayOpExpr; but the grammar will
* preferentially interpret such a construct as an ANY/ALL
* SubLink. To prevent misparsing the output that way, insert
* a dummy coercion (which will be stripped by parse analysis,
* so no inefficiency is added in dump and reload). This is
* indeed most likely what the user wrote to get the construct
* accepted in the first place.
*/
if (IsA(arg2, SubLink) && ((SubLink*)arg2)->subLinkType == EXPR_SUBLINK)
appendStringInfo(buf, "::%s", format_type_with_typemod(exprType(arg2), exprTypmod(arg2)));
appendStringInfoChar(buf, ')');
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_BoolExpr: {
BoolExpr* expr = (BoolExpr*)node;
Node* first_arg = (Node*)linitial(expr->args);
ListCell* arg = lnext(list_head(expr->args));
switch (expr->boolop) {
case AND_EXPR:
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(first_arg, context, false, node, no_alias);
while (arg != NULL) {
appendStringInfo(buf, " AND ");
get_rule_expr_paren((Node*)lfirst(arg), context, false, node, no_alias);
arg = lnext(arg);
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
break;
case OR_EXPR:
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(first_arg, context, false, node, no_alias);
while (arg != NULL) {
appendStringInfo(buf, " OR ");
get_rule_expr_paren((Node*)lfirst(arg), context, false, node, no_alias);
arg = lnext(arg);
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
break;
case NOT_EXPR:
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
appendStringInfo(buf, "NOT ");
get_rule_expr_paren(first_arg, context, false, node, no_alias);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized boolop: %d", (int)expr->boolop)));
}
} break;
case T_SubLink:
get_sublink_expr((SubLink*)node, context);
break;
case T_SubPlan: {
SubPlan* subplan = (SubPlan*)node;
* We cannot see an already-planned subplan in rule deparsing,
* only while EXPLAINing a query plan. We don't try to
* reconstruct the original SQL, just reference the subplan
* that appears elsewhere in EXPLAIN's result.
*/
if (subplan->useHashTable)
appendStringInfo(buf, "(hashed %s)", subplan->plan_name);
else
appendStringInfo(buf, "(%s)", subplan->plan_name);
} break;
case T_AlternativeSubPlan: {
AlternativeSubPlan* asplan = (AlternativeSubPlan*)node;
ListCell* lc = NULL;
appendStringInfo(buf, "(alternatives: ");
foreach (lc, asplan->subplans) {
SubPlan* splan = (SubPlan*)lfirst(lc);
Assert(IsA(splan, SubPlan));
if (splan->useHashTable)
appendStringInfo(buf, "hashed %s", splan->plan_name);
else
appendStringInfo(buf, "%s", splan->plan_name);
if (lnext(lc))
appendStringInfo(buf, " or ");
}
appendStringInfo(buf, ")");
} break;
case T_FieldSelect: {
FieldSelect* fselect = (FieldSelect*)node;
Node* arg = (Node*)fselect->arg;
int fno = fselect->fieldnum;
const char* fieldname = NULL;
bool need_parens = false;
* Parenthesize the argument unless it's an ArrayRef or
* another FieldSelect. Note in particular that it would be
* WRONG to not parenthesize a Var argument; simplicity is not
* the issue here, having the right number of names is.
*/
need_parens = !IsA(arg, ArrayRef) && !IsA(arg, FieldSelect);
if (need_parens)
appendStringInfoChar(buf, '(');
get_rule_expr(arg, context, true, no_alias);
if (need_parens)
appendStringInfoChar(buf, ')');
* Get and print the field name.
*/
fieldname = get_name_for_var_field((Var*)arg, fno, 0, context);
appendStringInfo(buf, ".%s", quote_identifier(fieldname));
} break;
case T_FieldStore: {
FieldStore* fstore = (FieldStore*)node;
bool need_parens = false;
* There is no good way to represent a FieldStore as real SQL,
* so decompilation of INSERT or UPDATE statements should
* always use processIndirection as part of the
* statement-level syntax. We should only get here when
* EXPLAIN tries to print the targetlist of a plan resulting
* from such a statement. The plan case is even harder than
* ordinary rules would be, because the planner tries to
* collapse multiple assignments to the same field or subfield
* into one FieldStore; so we can see a list of target fields
* not just one, and the arguments could be FieldStores
* themselves. We don't bother to try to print the target
* field names; we just print the source arguments, with a
* ROW() around them if there's more than one. This isn't
* terribly complete, but it's probably good enough for
* EXPLAIN's purposes; especially since anything more would be
* either hopelessly confusing or an even poorer
* representation of what the plan is actually doing.
*/
need_parens = (list_length(fstore->newvals) != 1);
if (need_parens)
appendStringInfoString(buf, "ROW(");
get_rule_expr((Node*)fstore->newvals, context, showimplicit, no_alias);
if (need_parens)
appendStringInfoChar(buf, ')');
} break;
case T_RelabelType: {
RelabelType* relabel = (RelabelType*)node;
Node* arg = (Node*)relabel->arg;
if (relabel->relabelformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr_paren(arg, context, false, node, no_alias);
} else {
get_coercion_expr(arg, context, relabel->resulttype, relabel->resulttypmod, node);
}
} break;
case T_CoerceViaIO: {
CoerceViaIO* iocoerce = (CoerceViaIO*)node;
Node* arg = (Node*)iocoerce->arg;
if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr_paren(arg, context, false, node, no_alias);
} else {
get_coercion_expr(arg, context, iocoerce->resulttype, -1, node);
}
} break;
case T_ArrayCoerceExpr: {
ArrayCoerceExpr* acoerce = (ArrayCoerceExpr*)node;
Node* arg = (Node*)acoerce->arg;
if (acoerce->coerceformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr_paren(arg, context, false, node, no_alias);
} else {
get_coercion_expr(arg, context, acoerce->resulttype, acoerce->resulttypmod, node);
}
} break;
case T_ConvertRowtypeExpr: {
ConvertRowtypeExpr* convert = (ConvertRowtypeExpr*)node;
Node* arg = (Node*)convert->arg;
if (convert->convertformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr_paren(arg, context, false, node, no_alias);
} else {
get_coercion_expr(arg, context, convert->resulttype, -1, node);
}
} break;
case T_CollateExpr: {
CollateExpr* collate = (CollateExpr*)node;
Node* arg = (Node*)collate->arg;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, showimplicit, node, no_alias);
appendStringInfo(buf, " COLLATE %s", generate_collation_name(collate->collOid));
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_CaseExpr: {
CaseExpr* caseexpr = (CaseExpr*)node;
ListCell* temp = NULL;
appendContextKeyword(context, "CASE", 0, PRETTYINDENT_VAR, 0);
if (caseexpr->arg) {
appendStringInfoChar(buf, ' ');
get_rule_expr((Node*)caseexpr->arg, context, true);
}
foreach (temp, caseexpr->args) {
CaseWhen* when = (CaseWhen*)lfirst(temp);
Node* w = (Node*)when->expr;
if (caseexpr->arg) {
* The parser should have produced WHEN clauses of the
* form "CaseTestExpr = RHS", possibly with an
* implicit coercion inserted above the CaseTestExpr.
* For accurate decompilation of rules it's essential
* that we show just the RHS. However in an
* expression that's been through the optimizer, the
* WHEN clause could be almost anything (since the
* equality operator could have been expanded into an
* inline function). If we don't recognize the form
* of the WHEN clause, just punt and display it as-is.
*/
if (IsA(w, OpExpr)) {
List* args = ((OpExpr*)w)->args;
if (list_length(args) == 2 &&
IsA(strip_implicit_coercions((Node*)linitial(args)), CaseTestExpr))
w = (Node*)lsecond(args);
}
}
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "WHEN ", 0, 0, 0);
get_rule_expr(w, context, false, no_alias);
appendStringInfo(buf, " THEN ");
get_rule_expr((Node*)when->result, context, true, no_alias);
}
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "ELSE ", 0, 0, 0);
get_rule_expr((Node*)caseexpr->defresult, context, true, no_alias);
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "END", -PRETTYINDENT_VAR, 0, 0);
} break;
case T_CaseTestExpr: {
* Normally we should never get here, since for expressions
* that can contain this node type we attempt to avoid
* recursing to it. But in an optimized expression we might
* be unable to avoid that (see comments for CaseExpr). If we
* do see one, print it as CASE_TEST_EXPR.
*/
appendStringInfo(buf, "CASE_TEST_EXPR");
} break;
case T_ArrayExpr: {
ArrayExpr* arrayexpr = (ArrayExpr*)node;
appendStringInfo(buf, "ARRAY[");
get_rule_expr((Node*)arrayexpr->elements, context, true, no_alias);
appendStringInfoChar(buf, ']');
* If the array isn't empty, we assume its elements are
* coerced to the desired type. If it's empty, though, we
* need an explicit coercion to the array type.
*/
if (arrayexpr->elements == NIL)
appendStringInfo(buf, "::%s", format_type_with_typemod(arrayexpr->array_typeid, -1));
} break;
case T_RowExpr: {
RowExpr* rowexpr = (RowExpr*)node;
TupleDesc tupdesc = NULL;
ListCell* arg = NULL;
int i;
char* sep = NULL;
* If it's a named type and not RECORD, we may have to skip
* dropped columns and/or claim there are NULLs for added
* columns.
*/
if (rowexpr->row_typeid != RECORDOID) {
tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
Assert(list_length(rowexpr->args) <= tupdesc->natts);
}
* SQL99 allows "ROW" to be omitted when there is more than
* one column, but for simplicity we always print it.
*/
appendStringInfo(buf, "ROW(");
sep = "";
i = 0;
foreach (arg, rowexpr->args) {
Node* e = (Node*)lfirst(arg);
if (tupdesc == NULL || !tupdesc->attrs[i].attisdropped) {
appendStringInfoString(buf, sep);
get_rule_expr(e, context, true, no_alias);
sep = ", ";
}
i++;
}
if (tupdesc != NULL) {
while (i < tupdesc->natts) {
if (!tupdesc->attrs[i].attisdropped) {
appendStringInfoString(buf, sep);
appendStringInfo(buf, "NULL");
sep = ", ";
}
i++;
}
ReleaseTupleDesc(tupdesc);
}
appendStringInfo(buf, ")");
if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
appendStringInfo(buf, "::%s", format_type_with_typemod(rowexpr->row_typeid, -1));
} break;
case T_RowCompareExpr: {
RowCompareExpr* rcexpr = (RowCompareExpr*)node;
ListCell* arg = NULL;
char* sep = NULL;
* SQL99 allows "ROW" to be omitted when there is more than
* one column, but for simplicity we always print it.
*/
appendStringInfo(buf, "(ROW(");
sep = "";
foreach (arg, rcexpr->largs) {
Node* e = (Node*)lfirst(arg);
appendStringInfoString(buf, sep);
get_rule_expr(e, context, true, no_alias);
sep = ", ";
}
* We assume that the name of the first-column operator will
* do for all the rest too. This is definitely open to
* failure, eg if some but not all operators were renamed
* since the construct was parsed, but there seems no way to
* be perfect.
*/
appendStringInfo(buf,
") %s ROW(",
generate_operator_name(linitial_oid(rcexpr->opnos),
exprType((Node*)linitial(rcexpr->largs)),
exprType((Node*)linitial(rcexpr->rargs))));
sep = "";
foreach (arg, rcexpr->rargs) {
Node* e = (Node*)lfirst(arg);
appendStringInfoString(buf, sep);
get_rule_expr(e, context, true, no_alias);
sep = ", ";
}
appendStringInfo(buf, "))");
} break;
case T_CoalesceExpr: {
CoalesceExpr* coalesceexpr = (CoalesceExpr*)node;
appendStringInfo(buf, "COALESCE(");
get_rule_expr((Node*)coalesceexpr->args, context, true, no_alias);
appendStringInfoChar(buf, ')');
} break;
case T_MinMaxExpr: {
MinMaxExpr* minmaxexpr = (MinMaxExpr*)node;
switch (minmaxexpr->op) {
case IS_GREATEST:
appendStringInfo(buf, "GREATEST(");
break;
case IS_LEAST:
appendStringInfo(buf, "LEAST(");
break;
default:
break;
}
get_rule_expr((Node*)minmaxexpr->args, context, true, no_alias);
appendStringInfoChar(buf, ')');
} break;
case T_XmlExpr: {
XmlExpr* xexpr = (XmlExpr*)node;
bool needcomma = false;
ListCell* arg = NULL;
ListCell* narg = NULL;
Const* con = NULL;
switch (xexpr->op) {
case IS_XMLCONCAT:
appendStringInfoString(buf, "XMLCONCAT(");
break;
case IS_XMLELEMENT:
appendStringInfoString(buf, "XMLELEMENT(");
break;
case IS_XMLFOREST:
appendStringInfoString(buf, "XMLFOREST(");
break;
case IS_XMLPARSE:
appendStringInfoString(buf, "XMLPARSE(");
break;
case IS_XMLPI:
appendStringInfoString(buf, "XMLPI(");
break;
case IS_XMLROOT:
appendStringInfoString(buf, "XMLROOT(");
break;
case IS_XMLSERIALIZE:
appendStringInfoString(buf, "XMLSERIALIZE(");
break;
case IS_DOCUMENT:
break;
default:
break;
}
if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE) {
if (xexpr->xmloption == XMLOPTION_DOCUMENT)
appendStringInfoString(buf, "DOCUMENT ");
else
appendStringInfoString(buf, "CONTENT ");
}
if (xexpr->name) {
appendStringInfo(buf, "NAME %s", quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
needcomma = true;
}
if (xexpr->named_args) {
if (xexpr->op != IS_XMLFOREST) {
if (needcomma)
appendStringInfoString(buf, ", ");
appendStringInfoString(buf, "XMLATTRIBUTES(");
needcomma = false;
}
forboth(arg, xexpr->named_args, narg, xexpr->arg_names)
{
Node* e = (Node*)lfirst(arg);
char* argname = strVal(lfirst(narg));
if (needcomma)
appendStringInfoString(buf, ", ");
get_rule_expr((Node*)e, context, true, no_alias);
appendStringInfo(buf, " AS %s", quote_identifier(map_xml_name_to_sql_identifier(argname)));
needcomma = true;
}
if (xexpr->op != IS_XMLFOREST)
appendStringInfoChar(buf, ')');
}
if (xexpr->args) {
if (needcomma)
appendStringInfoString(buf, ", ");
switch (xexpr->op) {
case IS_XMLCONCAT:
case IS_XMLELEMENT:
case IS_XMLFOREST:
case IS_XMLPI:
case IS_XMLSERIALIZE:
get_rule_expr((Node*)xexpr->args, context, true, no_alias);
break;
case IS_XMLPARSE:
Assert(list_length(xexpr->args) == 2);
get_rule_expr((Node*)linitial(xexpr->args), context, true, no_alias);
con = (Const*)lsecond(xexpr->args);
Assert(IsA(con, Const));
Assert(!con->constisnull);
if (DatumGetBool(con->constvalue))
appendStringInfoString(buf, " PRESERVE WHITESPACE");
else
appendStringInfoString(buf, " STRIP WHITESPACE");
break;
case IS_XMLROOT:
Assert(list_length(xexpr->args) == 3);
get_rule_expr((Node*)linitial(xexpr->args), context, true, no_alias);
appendStringInfoString(buf, ", VERSION ");
con = (Const*)lsecond(xexpr->args);
if (IsA(con, Const) && con->constisnull)
appendStringInfoString(buf, "NO VALUE");
else
get_rule_expr((Node*)con, context, false, no_alias);
con = (Const*)lthird(xexpr->args);
Assert(IsA(con, Const));
if (con->constisnull)
;
else {
switch (DatumGetInt32(con->constvalue)) {
case XML_STANDALONE_YES:
appendStringInfoString(buf, ", STANDALONE YES");
break;
case XML_STANDALONE_NO:
appendStringInfoString(buf, ", STANDALONE NO");
break;
case XML_STANDALONE_NO_VALUE:
appendStringInfoString(buf, ", STANDALONE NO VALUE");
break;
default:
break;
}
}
break;
case IS_DOCUMENT:
get_rule_expr_paren((Node*)xexpr->args, context, false, node, no_alias);
break;
default:
break;
}
}
if (xexpr->op == IS_XMLSERIALIZE)
appendStringInfo(buf, " AS %s", format_type_with_typemod(xexpr->type, xexpr->typmod));
if (xexpr->op == IS_DOCUMENT)
appendStringInfoString(buf, " IS DOCUMENT");
else
appendStringInfoChar(buf, ')');
} break;
case T_PriorExpr: {
appendContextKeyword(context, "Prior ", 0, 0, 0);
PriorExpr* prior_expr = (PriorExpr*)node;
get_rule_expr(prior_expr->node, context, showimplicit, no_alias);
} break;
case T_NullTest: {
NullTest* ntest = (NullTest*)node;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node*)ntest->arg, context, true, node, no_alias);
* For scalar inputs, we prefer to print as IS [NOT] NULL,
* which is shorter and traditional. If it's a rowtype input
* but we're applying a scalar test, must print IS [NOT]
* DISTINCT FROM NULL to be semantically correct.
*/
if (ntest->argisrow || !type_is_rowtype(exprType((Node *) ntest->arg))) {
switch (ntest->nulltesttype)
{
case IS_NULL:
appendStringInfoString(buf, " IS NULL");
break;
case IS_NOT_NULL:
appendStringInfoString(buf, " IS NOT NULL");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized nulltesttype: %d", (int)ntest->nulltesttype)));
}
} else {
switch (ntest->nulltesttype)
{
case IS_NULL:
appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL");
break;
case IS_NOT_NULL:
appendStringInfoString(buf, " IS DISTINCT FROM NULL");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized nulltesttype: %d", (int)ntest->nulltesttype)));
}
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_NanTest: {
NanTest* ntest = (NanTest*)node;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node*)ntest->arg, context, true, node, no_alias);
switch (ntest->nantesttype)
{
case IS_NAN:
appendStringInfoString(buf, " IS NAN");
break;
case IS_NOT_NAN:
appendStringInfoString(buf, " IS NOT NAN");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized nantesttype: %d", (int)ntest->nantesttype)));
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_InfiniteTest: {
InfiniteTest* ntest = (InfiniteTest*)node;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node*)ntest->arg, context, true, node, no_alias);
switch (ntest->infinitetesttype)
{
case IS_INFINITE:
appendStringInfoString(buf, " IS INFINITE");
break;
case IS_NOT_INFINITE:
appendStringInfoString(buf, " IS NOT INFINITE");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized infinitetesttype: %d", (int)ntest->infinitetesttype)));
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_HashFilter: {
HashFilter* htest = (HashFilter*)node;
ListCell* distVar = NULL;
char* sep = "";
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
appendStringInfo(buf, "Hash By ");
foreach (distVar, htest->arg) {
appendStringInfoString(buf, sep);
Node* distriVar = (Node*)lfirst(distVar);
get_rule_expr_paren(distriVar, context, true, node, no_alias);
sep = ", ";
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_BooleanTest: {
BooleanTest* btest = (BooleanTest*)node;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node*)btest->arg, context, false, node, no_alias);
switch (btest->booltesttype) {
case IS_TRUE:
appendStringInfo(buf, " IS TRUE");
break;
case IS_NOT_TRUE:
appendStringInfo(buf, " IS NOT TRUE");
break;
case IS_FALSE:
appendStringInfo(buf, " IS FALSE");
break;
case IS_NOT_FALSE:
appendStringInfo(buf, " IS NOT FALSE");
break;
case IS_UNKNOWN:
appendStringInfo(buf, " IS UNKNOWN");
break;
case IS_NOT_UNKNOWN:
appendStringInfo(buf, " IS NOT UNKNOWN");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized booltesttype: %d", (int)btest->booltesttype)));
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
} break;
case T_CoerceToDomain: {
CoerceToDomain* ctest = (CoerceToDomain*)node;
Node* arg = (Node*)ctest->arg;
if (ctest->coercionformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr(arg, context, false, no_alias);
} else {
get_coercion_expr(arg, context, ctest->resulttype, ctest->resulttypmod, node);
}
} break;
case T_CoerceToDomainValue:
appendStringInfo(buf, "VALUE");
break;
case T_SetToDefault:
appendStringInfo(buf, "DEFAULT");
break;
case T_CurrentOfExpr: {
CurrentOfExpr* cexpr = (CurrentOfExpr*)node;
if (cexpr->cursor_name)
appendStringInfo(buf, "CURRENT OF %s", quote_identifier(cexpr->cursor_name));
else
appendStringInfo(buf, "CURRENT OF $%d", cexpr->cursor_param);
} break;
case T_List: {
char* sep = NULL;
ListCell* l = NULL;
sep = "";
foreach (l, (List*)node) {
appendStringInfoString(buf, sep);
get_rule_expr((Node*)lfirst(l), context, showimplicit, no_alias);
sep = ", ";
}
} break;
case T_AutoIncrement:
appendStringInfo(buf, "AUTO_INCREMENT");
break;
case T_PrefixKey: {
PrefixKey* pkey = (PrefixKey*)node;
get_rule_expr((Node*)pkey->arg, context, showimplicit, no_alias);
appendStringInfo(buf, "(%d)", pkey->length);
} break;
case T_CursorExpression: {
CursorExpression* stmt = (CursorExpression*) node;
appendStringInfo(buf, "CURSOR(%s)", stmt->raw_query_str);
} break;
case T_TypeCast: {
TypeCast* tc = (TypeCast*) node;
if (showimplicit) {
get_coercion_expr(tc->arg, context, tc->typname->typeOid, -1, node);
} else {
get_rule_expr_paren(tc->arg, context, false, node, no_alias);
}
} break;
#ifdef USE_SPQ
case T_DMLActionExpr:
appendStringInfo(buf, "DMLAction");
break;
#endif
default:
if (context->qrw_phase)
appendStringInfo(buf, "<unknown %d>", (int)nodeTag(node));
else
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized node type: %d", (int)nodeTag(node))));
break;
}
}
* @Description:
* get_rule_expr_funccall - Parse back a function-call expression
*
* Same as get_rule_expr(), except that we guarantee that the output will
* look like a function call, or like one of the things the grammar treats as
* equivalent to a function call (see the func_expr_windowless production).
* This is needed in places where the grammar uses func_expr_windowless and
* you can't substitute a parenthesized a_expr. If what we have isn't going
* to look like a function call, wrap it in a dummy CAST() expression, which
* will satisfy the grammar --- and, indeed, is likely what the user wrote to
* produce such a thing.
* @in node - expr node
* @in context - deparse context
* @in showimplicit - whether display implicit casts for arguments of functions and operators
* @out - void
*/
static void get_rule_expr_funccall(Node* node, deparse_context* context, bool showimplicit)
{
if (looks_like_function(node)) {
get_rule_expr(node, context, showimplicit);
} else {
StringInfo buf = context->buf;
appendStringInfoString(buf, "CAST(");
get_rule_expr(node, context, false);
appendStringInfo(buf, " AS %s)", format_type_with_typemod(exprType(node), exprTypmod(node)));
}
}
* @Description: Helper function to identify node types that satisfy func_expr_windowless.
* If in doubt, "false" is always a safe answer.
* @in node - expr node
* @out - bool
*/
static bool looks_like_function(Node* node)
{
if (node == NULL)
return false;
switch (nodeTag(node)) {
case T_FuncExpr:
return (((FuncExpr*)node)->funcformat == COERCE_EXPLICIT_CALL);
case T_NullIfExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_XmlExpr:
return true;
default:
break;
}
return false;
}
* get_oper_expr - Parse back an OpExpr node
*/
static void get_oper_expr(OpExpr* expr, deparse_context* context, bool no_alias)
{
StringInfo buf = context->buf;
Oid opno = expr->opno;
List* args = expr->args;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
if (list_length(args) == 2) {
Node* arg1 = (Node*)linitial(args);
Node* arg2 = (Node*)lsecond(args);
get_rule_expr_paren(arg1, context, true, (Node*)expr, no_alias);
appendStringInfo(buf, " %s ", generate_operator_name(opno, exprType(arg1), exprType(arg2)));
get_rule_expr_paren(arg2, context, true, (Node*)expr, no_alias);
} else {
Node* arg = (Node*)linitial(args);
HeapTuple tp;
Form_pg_operator optup;
tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
if (!HeapTupleIsValid(tp))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for operator %u", opno)));
optup = (Form_pg_operator)GETSTRUCT(tp);
switch (optup->oprkind) {
case 'l':
appendStringInfo(buf, "%s ", generate_operator_name(opno, InvalidOid, exprType(arg)));
get_rule_expr_paren(arg, context, true, (Node*)expr, no_alias);
break;
case 'r':
get_rule_expr_paren(arg, context, true, (Node*)expr, no_alias);
appendStringInfo(buf, " %s", generate_operator_name(opno, exprType(arg), InvalidOid));
break;
default:
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("bogus oprkind: %d", optup->oprkind))));
}
ReleaseSysCache(tp);
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
}
* get_func_expr - Parse back a FuncExpr node
*/
static void get_func_expr(FuncExpr* expr, deparse_context* context, bool showimplicit)
{
StringInfo buf = context->buf;
Oid funcoid = expr->funcid;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
List* argnames = NIL;
bool use_variadic = false;
ListCell* l = NULL;
* If the function call came from an implicit coercion, then just show the
* first argument --- unless caller wants to see implicit coercions.
*/
if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit) {
get_rule_expr_paren((Node*)linitial(expr->args), context, false, (Node*)expr);
return;
}
* If the function call came from a cast, then show the first argument
* plus an explicit cast operation.
*/
if (expr->funcformat == COERCE_EXPLICIT_CAST || expr->funcformat == COERCE_IMPLICIT_CAST) {
Node* arg = (Node*)linitial(expr->args);
Oid rettype = expr->funcresulttype;
int32 coercedTypmod = -1;
if (false == context->is_fqs || expr->funcformat != COERCE_IMPLICIT_CAST)
(void)exprIsLengthCoercion((Node*)expr, &coercedTypmod);
get_coercion_expr(arg, context, rettype, coercedTypmod, (Node*)expr);
list_free_ext(argnames);
return;
}
* Normal function: display as proname(args). First we need to extract
* the argument datatypes.
*/
if (list_length(expr->args) > FUNC_MAX_ARGS)
ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("too many arguments")));
nargs = 0;
argnames = NIL;
foreach (l, expr->args) {
Node* arg = (Node*)lfirst(l);
if (IsA(arg, NamedArgExpr))
argnames = lappend(argnames, ((NamedArgExpr*)arg)->name);
argtypes[nargs] = exprType(arg);
nargs++;
}
appendStringInfo(
buf, "%s(", generate_function_name(funcoid, nargs, argnames, argtypes, expr->funcvariadic, &use_variadic,
context->skip_lock));
nargs = 0;
foreach (l, expr->args) {
if (nargs++ > 0)
appendStringInfoString(buf, ", ");
if (use_variadic && lnext(l) == NULL)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr((Node*)lfirst(l), context, true);
}
appendStringInfoChar(buf, ')');
}
* get_agg_expr - Parse back an Aggref node
*/
static void get_agg_expr(Aggref* aggref, deparse_context* context)
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
bool use_variadic = false;
#ifdef PGXC
bool added_finalfn = false;
#endif
nargs = get_aggregate_argtypes(aggref, argtypes, FUNC_MAX_ARGS);
#ifdef PGXC
* Datanode should send finalised aggregate results. Datanodes evaluate only
* transition results. In order to get the finalised aggregate, we enclose
* the aggregate call inside final function call, so as to get finalised
* results at the Coordinator
*/
if (context->finalise_aggs) {
HeapTuple aggTuple;
Form_pg_aggregate aggform;
aggTuple = SearchSysCache(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid), 0, 0, 0);
if (!HeapTupleIsValid(aggTuple))
ereport(ERROR,
(errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for aggregate %u", aggref->aggfnoid)));
aggform = (Form_pg_aggregate)GETSTRUCT(aggTuple);
if (OidIsValid(aggform->aggfinalfn)) {
appendStringInfo(buf, "%s(", generate_function_name(aggform->aggfinalfn, 0, NULL, NULL, false, NULL));
added_finalfn = true;
}
ReleaseSysCache(aggTuple);
}
#endif
char* funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, argtypes, aggref->aggvariadic, &use_variadic);
appendStringInfo(buf, "%s(%s", funcname, (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
bool isGroupCatAggFunc = aggref->aggfnoid == GROUPCONCATFUNCOID;
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) {
* Ordered-set aggregates do not use "*" syntax and we needn't
* worry about inserting VARIADIC. Also it's "Order by"
* inputs are their aggregate arguments. So we can directly get
* args as-is.
*/
Assert(!aggref->aggvariadic);
get_rule_expr((Node*)aggref->aggdirectargs, context, true);
Assert(aggref->aggorder != NIL);
appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
} else {
if (aggref->aggstar)
appendStringInfoChar(buf, '*');
else {
ListCell* l = NULL;
ListCell* init = list_head(aggref->args);
int narg = 0;
int start = 0;
if (isGroupCatAggFunc) {
init = init->next;
narg++;
start++;
}
for_each_cell (l, init) {
TargetEntry* tle = (TargetEntry*)lfirst(l);
Assert(!IsA((Node*)tle->expr, NamedArgExpr));
if (tle->resjunk)
continue;
if (narg++ > start)
appendStringInfoString(buf, ", ");
if (use_variadic && narg == nargs)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr((Node*)tle->expr, context, true);
}
}
if (aggref->aggiskeep) {
appendStringInfo(buf, ") KEEP(DENSE_RANK %s", (aggref->aggkpfirst ? "FIRST" : "LAST"));
}
if (aggref->aggorder != NIL) {
Assert(funcname);
if (pg_strcasecmp(funcname, "listagg") == 0) {
appendStringInfoString(buf, " ) WITHIN GROUP ( ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
} else {
appendStringInfoString(buf, " ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
}
if (aggref->aggfilter != NULL) {
appendStringInfoString(buf, ") FILTER (WHERE ");
get_rule_expr((Node *)aggref->aggfilter, context, false);
}
if (isGroupCatAggFunc) {
appendStringInfoString(buf, " SEPARATOR ");
Const* con = (Const*)(((TargetEntry*)lfirst(list_head(aggref->args)))->expr);
get_rule_separator(con, buf);
}
}
#ifdef PGXC
if (added_finalfn)
appendStringInfoChar(buf, ')');
#endif
appendStringInfoChar(buf, ')');
}
static bool construct_partitionClause(WindowAgg* node, WindowClause* wc)
{
List* partitionClause = NIL;
for (int i = 0; i < node->partNumCols; i++) {
TargetEntry* tle = NULL;
SortGroupClause* partcl = makeNode(SortGroupClause);
tle = get_tle_by_resno(node->plan.lefttree->targetlist, node->partColIdx[i]);
if (tle == NULL) {
return false;
}
* ressortgroupref refers to windowagg's tlist
* partColIdx refers to subplan's tlist
*/
#ifdef USE_SPQ
wc->rePartitionSPQ = false;
if (IS_SPQ_COORDINATOR) {
if (IsA(tle->expr, Var)) {
Var* tle_expr = (Var*)tle->expr;
partcl->tleSortGroupRef = tle_expr->varattno;
wc->rePartitionSPQ = true;
} else {
list_free_ext(partitionClause);
return false;
}
} else
#endif
{
ListCell *lc = NULL;
foreach(lc, node->plan.targetlist) {
TargetEntry *window_agg_te = (TargetEntry *)lfirst(lc);
if (IsA(tle->expr, Var) && IsA(window_agg_te->expr, Var) &&
_equalSimpleVar(tle->expr, window_agg_te->expr)) {
if (window_agg_te->ressortgroupref > 0) {
partcl->tleSortGroupRef = window_agg_te->ressortgroupref;
break;
}
}
}
if (lc == NULL) {
list_free_ext(partitionClause);
return false;
}
}
partcl->eqop = node->partOperators[i];
partitionClause = lappend(partitionClause, partcl);
}
wc->partitionClause = partitionClause;
return true;
}
* construct_windowClause - Construct a WindowClause node for parser the param of OVER
*/
static void construct_windowClause(deparse_context* context)
{
deparse_namespace* dpns = (deparse_namespace*)linitial(context->namespaces);
PlanState* ps = dpns->planstate;
WindowAgg* node = NULL;
WindowClause* wc = makeNode(WindowClause);
List* orderClause = NIL;
bool isParaInValid = (NULL == ps) || (NULL == ps->plan) || ((!IsA(ps->plan, WindowAgg)) &&
(!IsA(ps->plan, VecWindowAgg)));
if (isParaInValid) {
return;
}
node = (WindowAgg*)ps->plan;
if (node->partNumCols > 0 && !construct_partitionClause(node, wc)) {
return;
}
if (node->ordNumCols > 0) {
for (int i = 0; i < node->ordNumCols; i++) {
TargetEntry* tle = NULL;
SortGroupClause* sortcl = makeNode(SortGroupClause);
tle = get_tle_by_resno(node->plan.lefttree->targetlist, node->ordColIdx[i]);
if (tle == NULL) {
return;
}
* ressortgroupref refers to windowagg's tlist
* partColIdx refers to subplan's tlist
*/
#ifdef USE_SPQ
wc->reOrderSPQ = false;
if (IS_SPQ_COORDINATOR) {
if (IsA(tle->expr, Var)) {
Var* tle_expr = (Var*)tle->expr;
sortcl->tleSortGroupRef = tle_expr->varattno;
wc->reOrderSPQ = true;
} else {
list_free_ext(orderClause);
return;
}
} else
#endif
{
ListCell *lc = NULL;
foreach(lc, node->plan.targetlist) {
TargetEntry *window_agg_te = (TargetEntry *)lfirst(lc);
if (IsA(tle->expr, Var) && IsA(window_agg_te->expr, Var) &&
_equalSimpleVar(tle->expr, window_agg_te->expr)) {
if (window_agg_te->ressortgroupref > 0) {
sortcl->tleSortGroupRef = window_agg_te->ressortgroupref;
break;
}
}
}
if (lc == NULL) {
list_free_ext(orderClause);
return;
}
}
sortcl->sortop = node->ordOperators[i];
orderClause = lappend(orderClause, sortcl);
}
wc->orderClause = orderClause;
}
wc->frameOptions = node->frameOptions;
wc->startOffset = node->startOffset;
wc->endOffset = node->endOffset;
wc->winref = node->winref;
wc->copiedOrder = false;
context->windowClause = list_make1(wc);
context->windowTList = node->plan.targetlist;
}
static void get_keep_orderby(List* argList, List* orderList, bool keepfirst, deparse_context* context)
{
if (list_length(argList) <= 0) {
return;
}
StringInfo buf = context->buf;
const char* sep = NULL;
ListCell* arg_l = NULL, *arg_lc;
appendStringInfo(buf, ") KEEP(DENSE_RANK %s ORDER BY ", (keepfirst ? "FIRST" : "LAST"));
sep = "";
forboth (arg_lc, argList, arg_l, orderList) {
SortGroupClause* srt = (SortGroupClause*)lfirst(arg_l);
Node *arg = (Node*)lfirst(arg_lc);
Oid sortcoltype;
TypeCacheEntry* typentry = NULL;
appendStringInfoString(buf, sep);
get_rule_expr(arg, context, true);
sortcoltype = exprType(arg);
typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
if (srt->sortop == typentry->lt_opr) {
if (srt->nulls_first)
appendStringInfo(buf, " NULLS FIRST");
} else if (srt->sortop == typentry->gt_opr) {
appendStringInfo(buf, " DESC");
if (!srt->nulls_first)
appendStringInfo(buf, " NULLS LAST");
} else {
appendStringInfo(buf, " USING %s", generate_operator_name(srt->sortop, sortcoltype, sortcoltype));
if (srt->nulls_first)
appendStringInfo(buf, " NULLS FIRST");
else
appendStringInfo(buf, " NULLS LAST");
}
sep = ", ";
}
}
* get_windowfunc_expr - Parse back a WindowFunc node
*/
static void get_windowfunc_expr(WindowFunc* wfunc, deparse_context* context)
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
List* argnames = NIL;
ListCell* arg_l = NULL;
char* funcname = NULL;
if (list_length(wfunc->args) > FUNC_MAX_ARGS)
ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("too many arguments")));
nargs = 0;
foreach (arg_l, wfunc->args) {
Node* arg = (Node*)lfirst(arg_l);
if (IsA(arg, NamedArgExpr))
argnames = lappend(argnames, ((NamedArgExpr*)arg)->name);
argtypes[nargs] = exprType(arg);
nargs++;
}
funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, argtypes, false, NULL);
if (pg_strcasecmp(funcname,"\"nth_value\"") == 0) {
funcname = "nth_value";
}
appendStringInfo(buf, "%s(", funcname);
if (wfunc->winstar)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node*)wfunc->args, context, true);
get_keep_orderby(wfunc->keep_args, wfunc->winkporder, wfunc->winkpfirst, context);
Assert(funcname);
if (pg_strcasecmp(funcname, "listagg") == 0)
appendStringInfoString(buf, ") WITHIN GROUP ");
else {
appendStringInfoString(buf, ")");
if (wfunc->is_from_last)
appendStringInfoString(buf, " FROM LAST");
if (wfunc->is_ignore_nulls)
appendStringInfoString(buf, " IGNORE NULLS");
appendStringInfoString(buf, " OVER ");
}
construct_windowClause(context);
foreach (arg_l, context->windowClause) {
WindowClause* wc = (WindowClause*)lfirst(arg_l);
if (wc->winref == wfunc->winref) {
if (wc->name)
appendStringInfoString(buf, quote_identifier(wc->name));
else {
if (pg_strcasecmp(funcname, "listagg") == 0)
get_rule_windowspec_listagg(wc, context->windowTList, context);
else
get_rule_windowspec(wc, context->windowTList, context);
}
break;
}
}
if (arg_l == NULL) {
if (context->windowClause != NULL)
ereport(ERROR, (errmodule(MOD_OPT), (errcode(ERRCODE_WINDOWING_ERROR),
errmsg("could not find window clause for winref %u", wfunc->winref))));
* In EXPLAIN, we don't have window context information available, so
* we have to settle for this:
*/
appendStringInfoString(buf, "(?)");
}
}
* get_coercion_expr
*
* Make a string representation of a value coerced to a specific type
* ----------
*/
static void get_coercion_expr(Node* arg, deparse_context* context, Oid resulttype, int32 resulttypmod, Node* parentNode)
{
StringInfo buf = context->buf;
* Since parse_coerce.c doesn't immediately collapse application of
* length-coercion functions to constants, what we'll typically see in
* such cases is a Const with typmod -1 and a length-coercion function
* right above it. Avoid generating redundant output. However, beware of
* suppressing casts when the user actually wrote something like
* 'foo'::text::char(3).
*/
if (arg && IsA(arg, Const) && ((Const*)arg)->consttype == resulttype && ((Const*)arg)->consttypmod == -1) {
get_const_expr((Const*)arg, context, -1);
} else {
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, false, parentNode);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
}
* Never emit resulttype(arg) functional notation. A pg_proc entry could
* take precedence, and a resulttype in pg_temp would require schema
* qualification that format_type_with_typemod() would usually omit. We've
* standardized on arg::resulttype, but CAST(arg AS resulttype) notation
* would work fine.
*/
appendStringInfo(buf, "::%s", format_type_with_typemod(resulttype, resulttypmod));
}
* get_const_expr
*
* Make a string representation of a Const
*
* showtype can be -1 to never show "::typename" decoration, or +1 to always
* show it, or 0 to show it only if the constant wouldn't be assumed to be
* the right type by default.
*
* If the Const's collation isn't default for its type, show that too.
* This can only happen in trees that have been through constant-folding.
* We assume we don't need to do this when showtype is -1.
* ----------
*/
static void get_const_expr(Const* constval, deparse_context* context, int showtype)
{
StringInfo buf = context->buf;
Oid typoutput;
bool typIsVarlena = false;
char* extval = NULL;
bool isfloat = false;
bool needlabel = false;
bool skip_collation = false;
if (constval->constisnull || constval->ismaxvalue) {
* Always label the type of a NULL/MAXVALUE constant to
* prevent misdecisions about type when reparsing.
*/
if (constval->ismaxvalue) {
appendStringInfo(buf, "MAXVALUE");
} else {
appendStringInfo(buf, "NULL");
}
if (showtype >= 0) {
appendStringInfo(buf, "::%s", format_type_with_typemod(constval->consttype, constval->consttypmod));
get_const_collation(constval, context);
}
return;
}
getTypeOutputInfo(constval->consttype, &typoutput, &typIsVarlena);
if (u_sess->exec_cxt.under_auto_explain)
extval = pstrdup("***");
else
extval = OidOutputFunctionCall(typoutput, constval->constvalue);
float8 num8 = 0;
float4 num4 = 0;
unsigned int ndig = 0;
bool iseq = true;
char* priStr = (char*)palloc(MAXDOUBLEWIDTH + 1);
errno_t rc = EOK;
switch (constval->consttype) {
case FLOAT8OID:
num8 = DatumGetFloat8(constval->constvalue);
ndig = DBL_DIG + u_sess->attr.attr_common.extra_float_digits;
rc = snprintf_s(priStr, MAXDOUBLEWIDTH + 1, MAXDOUBLEWIDTH, "%.*g", ndig + 2, num8);
securec_check_ss(rc, "", "");
if ((strspn(extval, "0123456789.") == ndig + 1 || strspn(extval, "0123456789") == ndig) &&
strncmp(priStr, extval, strlen(priStr) + 1))
iseq = false;
break;
case FLOAT4OID:
num4 = DatumGetFloat4(constval->constvalue);
ndig = FLT_DIG + u_sess->attr.attr_common.extra_float_digits;
rc = snprintf_s(priStr, MAXDOUBLEWIDTH + 1, MAXDOUBLEWIDTH, "%.*g", ndig + 2, num4);
securec_check_ss(rc, "", "");
if ((strspn(extval, "0123456789.") == ndig + 1 || strspn(extval, "0123456789") == ndig) &&
strncmp(priStr, extval, strlen(priStr) + 1))
iseq = false;
break;
default:
break;
}
switch (constval->consttype) {
case FLOAT4OID:
case FLOAT8OID: {
if (strspn(extval, "0123456789+-eE.") == strlen(extval)) {
if (!iseq) {
if (extval[0] == '+' || extval[0] == '-')
appendStringInfo(buf, "(%s)", priStr);
else
appendStringInfoString(buf, priStr);
} else {
if (extval[0] == '+' || extval[0] == '-')
appendStringInfo(buf, "(%s)", extval);
else
appendStringInfoString(buf, extval);
}
if (strcspn(extval, "eE.") != strlen(extval))
isfloat = true;
} else {
if (!iseq)
appendStringInfo(buf, "'%s'", priStr);
else
appendStringInfo(buf, "'%s'", extval);
}
pfree_ext(priStr);
break;
}
case INT2OID:
case INT4OID:
case INT8OID:
case OIDOID:
case NUMERICOID: {
* These types are printed without quotes unless they contain
* values that aren't accepted by the scanner unquoted (e.g.,
* 'NaN'). Note that strtod() and friends might accept NaN,
* so we can't use that to test.
*
* In reality we only need to defend against infinity and NaN,
* so we need not get too crazy about pattern matching here.
*
* There is a special-case gotcha: if the constant is signed,
* we need to parenthesize it, else the parser might see a
* leading plus/minus as binding less tightly than adjacent
* operators --- particularly, the cast that we might attach
* below.
*/
if (strspn(extval, "0123456789+-eE.") == strlen(extval)) {
if (extval[0] == '+' || extval[0] == '-')
appendStringInfo(buf, "(%s)", extval);
else
appendStringInfoString(buf, extval);
if (strcspn(extval, "eE.") != strlen(extval))
isfloat = true;
} else
appendStringInfo(buf, "'%s'", extval);
} break;
case BITOID:
case VARBITOID:
appendStringInfo(buf, "B'%s'", extval);
break;
case BOOLOID:
if (strcmp(extval, "t") == 0)
appendStringInfo(buf, "true");
else
appendStringInfo(buf, "false");
break;
default:
int src_encoding = get_valid_charset_by_collation(constval->constcollid);
char* converted_str = (src_encoding != GetDatabaseEncoding()) ?
pg_any_to_server(extval, strlen(extval), src_encoding) : extval;
skip_collation = (src_encoding != GetCharsetConnection() || GetCollationConnection() == constval->constcollid);
if (converted_str != extval) {
simple_quote_literal(buf, converted_str);
pfree_ext(converted_str);
} else {
simple_quote_literal(buf, extval);
}
break;
}
pfree_ext(extval);
if (showtype < 0)
return;
* For showtype == 0, append ::typename unless the constant will be
* implicitly typed as the right type when it is read in.
*
* XXX this code has to be kept in sync with the behavior of the parser,
* especially make_const.
*/
switch (constval->consttype) {
case BOOLOID:
case INT4OID:
case UNKNOWNOID:
needlabel = false;
break;
case NUMERICOID:
* Float-looking constants will be typed as numeric, but if
* there's a specific typmod we need to show it.
*/
needlabel = !isfloat || (constval->consttypmod >= 0);
break;
default:
needlabel = true;
break;
}
if (needlabel || showtype > 0)
appendStringInfo(buf, "::%s", format_type_with_typemod(constval->consttype, constval->consttypmod));
if (skip_collation) {
return;
}
get_const_collation(constval, context);
}
* helper for get_const_expr: append COLLATE if needed
*/
static void get_const_collation(Const* constval, deparse_context* context)
{
StringInfo buf = context->buf;
if (OidIsValid(constval->constcollid)) {
Oid typcollation = get_typcollation(constval->consttype);
if (constval->constcollid != typcollation) {
appendStringInfo(buf, " COLLATE %s", generate_collation_name(constval->constcollid));
}
}
}
* simple_quote_literal - Format a string as a SQL literal, append to buf
*/
static void simple_quote_literal(StringInfo buf, const char* val)
{
const char* valptr = NULL;
* We form the string literal according to the prevailing setting of
* standard_conforming_strings; we never use E''. User is responsible for
* making sure result is used correctly.
*/
appendStringInfoChar(buf, '\'');
for (valptr = val; *valptr; valptr++) {
char ch = *valptr;
if (SQL_STR_DOUBLE(ch, !u_sess->attr.attr_sql.standard_conforming_strings))
appendStringInfoChar(buf, ch);
appendStringInfoChar(buf, ch);
}
appendStringInfoChar(buf, '\'');
}
static void set_string_info_left(SubLink* sublink, StringInfo buf)
{
if (sublink->subLinkType == ARRAY_SUBLINK)
appendStringInfo(buf, "ARRAY(");
else
appendStringInfoChar(buf, '(');
}
static void set_string_info_right(bool need_paren, StringInfo buf)
{
if (need_paren)
appendStringInfo(buf, "))");
else
appendStringInfoChar(buf, ')');
}
* @Description: Parse back a sublink
* @in sublink: Subselect appearing in an expression.
* @in context: Context info needed for invoking a recursive querytree display routine.
*/
static void get_sublink_expr(SubLink* sublink, deparse_context* context)
{
StringInfo buf = context->buf;
Query* query = (Query*)(sublink->subselect);
char* opname = NULL;
bool need_paren = false;
set_string_info_left(sublink, buf);
* Note that we print the name of only the first operator, when there are
* multiple combining operators. This is an approximation that could go
* wrong in various scenarios (operators in different schemas, renamed
* operators, etc) but there is not a whole lot we can do about it, since
* the syntax allows only one operator to be shown.
*/
if (sublink->testexpr) {
if (IsA(sublink->testexpr, OpExpr)) {
OpExpr* opexpr = (OpExpr*)sublink->testexpr;
* To sublink's testexpr, we need not add implicit cast, because
* we leave type of subquery's targetlist unchanged.
* If we only change one side, the type cast rule can be changed during reparse,
* like "create table as" case.
*/
get_rule_expr((Node*)linitial(opexpr->args), context, false);
opname = generate_operator_name(
opexpr->opno, exprType((Node*)linitial(opexpr->args)), exprType((Node*)lsecond(opexpr->args)));
} else if (IsA(sublink->testexpr, BoolExpr)) {
char* sep = NULL;
ListCell* l = NULL;
appendStringInfoChar(buf, '(');
sep = "";
foreach (l, ((BoolExpr*)sublink->testexpr)->args) {
OpExpr* opexpr = (OpExpr*)lfirst(l);
Assert(IsA(opexpr, OpExpr));
appendStringInfoString(buf, sep);
get_rule_expr((Node*)linitial(opexpr->args), context, false);
if (opname == NULL)
opname = generate_operator_name(
opexpr->opno, exprType((Node*)linitial(opexpr->args)), exprType((Node*)lsecond(opexpr->args)));
sep = ", ";
}
appendStringInfoChar(buf, ')');
} else if (IsA(sublink->testexpr, RowCompareExpr)) {
RowCompareExpr* rcexpr = (RowCompareExpr*)sublink->testexpr;
appendStringInfoChar(buf, '(');
get_rule_expr((Node*)rcexpr->largs, context, false);
opname = generate_operator_name(linitial_oid(rcexpr->opnos),
exprType((Node*)linitial(rcexpr->largs)),
exprType((Node*)linitial(rcexpr->rargs)));
appendStringInfoChar(buf, ')');
} else if (IsA(sublink->testexpr, Const)) {
get_rule_expr((Node*)sublink->testexpr, context, false);
set_string_info_right(need_paren, buf);
return;
} else
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized testexpr type: %d", (int)nodeTag(sublink->testexpr))));
}
need_paren = true;
switch (sublink->subLinkType) {
case EXISTS_SUBLINK:
appendStringInfo(buf, "EXISTS ");
break;
case ANY_SUBLINK:
Assert(opname != NULL);
if (strcmp(opname, "=") == 0)
appendStringInfo(buf, " IN ");
else
appendStringInfo(buf, " %s ANY ", opname);
break;
case ALL_SUBLINK:
appendStringInfo(buf, " %s ALL ", opname);
break;
case ROWCOMPARE_SUBLINK:
appendStringInfo(buf, " %s ", opname);
break;
case EXPR_SUBLINK:
case ARRAY_SUBLINK:
need_paren = false;
break;
case CTE_SUBLINK:
default:
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized sublink type: %d", (int)sublink->subLinkType))));
break;
}
if (need_paren)
appendStringInfoChar(buf, '(');
get_query_def(query,
buf,
context->namespaces,
NULL,
context->prettyFlags,
context->wrapColumn,
context->indentLevel
#ifdef PGXC
,
context->finalise_aggs,
context->sortgroup_colno,
context->parser_arg
#endif
,
context->qrw_phase,
context->viewdef,
context->is_fqs,
context->skip_lock);
set_string_info_right(need_paren, buf);
}
* get_from_clause - Parse back a FROM clause
*
* "prefix" is the keyword that denotes the start of the list of FROM
* elements. It is FROM when used to parse back SELECT and UPDATE, but
* is USING when parsing back DELETE.
* ----------
*/
static void get_from_clause(Query* query, const char* prefix, deparse_context* context, List* fromlist,
bool isNeedError)
{
StringInfo buf = context->buf;
bool first = true;
ListCell* l = NULL;
List* srclist = (fromlist != NIL) ? fromlist : query->jointree->fromlist;
* We use the query's jointree as a guide to what to print. However, we
* must ignore auto-added RTEs that are marked not inFromCl. (These can
* only appear at the top level of the jointree, so it's sufficient to
* check here.) This check also ensures we ignore the rule pseudo-RTEs
* for NEW and OLD.
*/
foreach (l, srclist) {
Node* jtnode = (Node*)lfirst(l);
if (IsA(jtnode, RangeTblRef)) {
int varno = ((RangeTblRef*)jtnode)->rtindex;
RangeTblEntry* rte = rt_fetch(varno, query->rtable);
if (!rte->inFromCl && (fromlist == NIL || varno != linitial_int(query->resultRelations)))
continue;
}
if (first) {
appendContextKeyword(context, prefix, -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
first = false;
get_from_clause_item(jtnode, query, context, isNeedError);
} else {
StringInfoData itembuf;
appendStringInfoString(buf, ", ");
* Put the new FROM item's text into itembuf so we can decide
* after we've got it whether or not it needs to go on a new line.
*/
initStringInfo(&itembuf);
context->buf = &itembuf;
get_from_clause_item(jtnode, query, context, isNeedError);
context->buf = buf;
if (PRETTY_INDENT(context) && context->wrapColumn >= 0) {
char* trailing_nl = NULL;
trailing_nl = strrchr(buf->data, '\n');
if (trailing_nl == NULL)
trailing_nl = buf->data;
else
trailing_nl++;
* Add a newline, plus some indentation, if the new item would
* cause an overflow.
*/
if (strlen(trailing_nl) + strlen(itembuf.data) > (unsigned int)(context->wrapColumn))
appendContextKeyword(context, "", -PRETTYINDENT_STD, PRETTYINDENT_STD, PRETTYINDENT_VAR);
}
appendStringInfoString(buf, itembuf.data);
pfree_ext(itembuf.data);
}
}
}
static void get_from_clause_bucket(RangeTblEntry* rte, StringInfo buf, deparse_context* context)
{
Assert(rte->isbucket);
appendStringInfo(buf, " BUCKETS(");
ListCell* lc = NULL;
foreach (lc, rte->buckets) {
appendStringInfo(buf, "%d", lfirst_int(lc));
if (lc != list_tail(rte->buckets)) {
appendStringInfo(buf, ",");
}
}
appendStringInfo(buf, ") ");
}
static void get_from_clause_partition(RangeTblEntry* rte, StringInfo buf, deparse_context* context)
{
Assert(rte->ispartrel);
if (rte->plist) {
ListCell* cell = NULL;
char* semicolon = "";
appendStringInfo(buf, " PARTITION FOR(");
foreach (cell, rte->plist) {
Node* col = (Node*)lfirst(cell);
appendStringInfoString(buf, semicolon);
get_rule_expr(processIndirection(col, context, false), context, false);
semicolon = " ,";
}
appendStringInfo(buf, ")");
} else if (rte->pname) {
Oid partitionOid = list_nth_oid(rte->partitionOidList, 0);
pfree(rte->pname->aliasname);
rte->pname->aliasname = getPartitionName(partitionOid, true);
if (rte->pname->aliasname == NULL && rte->partitionNameList != NIL) {
rte->pname->aliasname = strVal(list_nth(rte->partitionNameList, 0));
}
appendStringInfo(buf, " PARTITION(%s)", quote_identifier(rte->pname->aliasname));
}
}
static void get_from_clause_subpartition(RangeTblEntry* rte, StringInfo buf, deparse_context* context)
{
Assert(rte->ispartrel);
if (rte->plist) {
ListCell* cell = NULL;
char* semicolon = "";
appendStringInfo(buf, " SUBPARTITION FOR(");
foreach (cell, rte->plist) {
Node* col = (Node*)lfirst(cell);
appendStringInfoString(buf, semicolon);
get_rule_expr(processIndirection(col, context, false), context, false);
semicolon = " ,";
}
appendStringInfo(buf, ")");
} else if (rte->pname) {
Oid subpartitionOid = list_nth_oid(rte->subpartitionOidList, 0);
pfree(rte->pname->aliasname);
rte->pname->aliasname = getPartitionName(subpartitionOid, true);
if (rte->pname->aliasname == NULL && rte->subpartitionNameList != NIL) {
rte->pname->aliasname = strVal(list_nth(rte->subpartitionNameList, 0));
}
appendStringInfo(buf, " SUBPARTITION(%s)", quote_identifier(rte->pname->aliasname));
}
}
static void get_delete_from_partition_clause(RangeTblEntry* rte, StringInfo buf)
{
Assert(rte->ispartrel);
if (rte->pname || rte->plist) {
return;
}
ListCell *partCell = NULL;
ListCell *subpartCell = NULL;
Oid partitionOid;
char *name = NULL;
bool first = true;
appendStringInfo(buf, " PARTITION (");
ListCell *partNameCell = list_head(rte->partitionNameList);
ListCell *subpartNameCell = list_head(rte->subpartitionNameList);
char *nameStr = NULL;
forboth(partCell, rte->partitionOidList, subpartCell, rte->subpartitionOidList) {
partitionOid = lfirst_oid(subpartCell);
if (unlikely(subpartNameCell != NULL)) {
nameStr = strVal(lfirst(subpartNameCell));
}
if (!OidIsValid(partitionOid)) {
partitionOid = lfirst_oid(partCell);
if (unlikely(partNameCell != NULL)) {
nameStr = strVal(lfirst(partNameCell));
}
}
name = getPartitionName(partitionOid, true);
if (name == NULL) {
name = nameStr;
}
if (unlikely(partNameCell != NULL)) {
partNameCell = lnext(partNameCell);
}
if (unlikely(subpartNameCell != NULL)) {
subpartNameCell = lnext(subpartNameCell);
}
if (first) {
first = false;
} else {
appendStringInfoString(buf, ", ");
}
appendStringInfo(buf, "%s", quote_identifier(name));
}
appendStringInfo(buf, ")");
}
static void get_from_clause_item(Node* jtnode, Query* query, deparse_context* context, bool isNeedError)
{
StringInfo buf = context->buf;
if (IsA(jtnode, RangeTblRef)) {
int varno = ((RangeTblRef*)jtnode)->rtindex;
RangeTblEntry* rte = rt_fetch(varno, query->rtable);
bool gavealias = false;
if (rte->lateral)
appendStringInfoString(buf, "LATERAL ");
switch (rte->rtekind) {
case RTE_RELATION: {
if (OidIsValid(rte->refSynOid)) {
appendStringInfo(buf, "%s%s", only_marker(rte), GetQualifiedSynonymName(rte->refSynOid, true));
} else {
char *relname = generate_relation_name(rte->relid, context->namespaces, isNeedError);
appendStringInfo(
buf, "%s%s", only_marker(rte), relname == NULL ? rte->relname : relname
);
}
if (rte->orientation == REL_COL_ORIENTED || rte->orientation == REL_TIMESERIES_ORIENTED)
query->vec_output = true;
} break;
case RTE_SUBQUERY:
appendStringInfoChar(buf, '(');
get_query_def(rte->subquery,
buf,
context->namespaces,
NULL,
context->prettyFlags,
context->wrapColumn,
context->indentLevel
#ifdef PGXC
,
context->finalise_aggs,
context->sortgroup_colno,
context->parser_arg
#endif
,
context->qrw_phase,
context->viewdef,
context->is_fqs,
context->skip_lock);
appendStringInfoChar(buf, ')');
break;
case RTE_FUNCTION:
get_rule_expr_funccall(rte->funcexpr, context, true);
break;
case RTE_VALUES:
appendStringInfoChar(buf, '(');
get_values_def(rte->values_lists, context);
appendStringInfoChar(buf, ')');
break;
case RTE_CTE:
appendStringInfoString(buf, quote_identifier(rte->ctename));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized RTE kind: %d", (int)rte->rtekind)));
break;
}
if (rte->isContainPartition) {
get_from_clause_partition(rte, buf, context);
}
if (rte->isContainSubPartition) {
get_from_clause_subpartition(rte, buf, context);
}
if (rte->isbucket) {
get_from_clause_bucket(rte, buf, context);
}
char *relname = get_relation_name(rte->relid, isNeedError);
relname = relname == NULL ? rte->relname : relname;
if (rte->alias != NULL) {
appendStringInfo(buf, " %s", quote_identifier(rte->alias->aliasname));
gavealias = true;
} else if (rte->rtekind == RTE_RELATION && strcmp(rte->eref->aliasname, relname) != 0) {
* Apparently the rel has been renamed since the rule was made.
* Emit a fake alias clause so that variable references will still
* work. This is not a 100% solution but should work in most
* reasonable situations.
*/
appendStringInfo(buf, " %s", quote_identifier(rte->eref->aliasname));
gavealias = true;
}
#ifdef PGXC
else if (rte->rtekind == RTE_SUBQUERY && rte->eref->aliasname) {
*
* This condition arises when the from clause is a view. The
* corresponding subquery RTE has its eref set to view name.
* The remote query generated has this subquery of which the
* columns can be referred to as view_name.col1, so it should
* be possible to refer to this subquery object.
*/
appendStringInfo(buf, " %s", quote_identifier(rte->eref->aliasname));
gavealias = true;
}
#endif
else if (rte->rtekind == RTE_FUNCTION) {
* For a function RTE, always give an alias. This covers possible
* renaming of the function and/or instability of the
* FigureColname rules for things that aren't simple functions.
*/
appendStringInfo(buf, " %s", quote_identifier(rte->eref->aliasname));
gavealias = true;
} else if (rte->rtekind == RTE_VALUES) {
appendStringInfo(buf, " %s", quote_identifier(rte->eref->aliasname));
gavealias = true;
}
if (rte->rtekind == RTE_FUNCTION) {
if (rte->funccoltypes != NIL) {
if (!gavealias)
appendStringInfo(buf, " AS ");
get_from_clause_coldeflist(
rte->eref->colnames, rte->funccoltypes, rte->funccoltypmods, rte->funccolcollations, context);
} else {
* For a function RTE, always emit a complete column alias
* list; this is to protect against possible instability of
* the default column names (eg, from altering parameter
* names).
*/
get_from_clause_alias(rte->eref, rte, context);
}
} else {
* For non-function RTEs, just report whatever the user originally
* gave as column aliases.
*/
get_from_clause_alias(rte->alias, rte, context);
}
if (rte->rtekind == RTE_RELATION && rte->tablesample) {
get_tablesample_def(rte->tablesample, context);
}
if (rte->rtekind == RTE_RELATION && rte->timecapsule) {
GetTimecapsuleDef(rte->timecapsule, context);
}
if (list_length(rte->partitionOidList) > 0) {
get_delete_from_partition_clause(rte, buf);
}
} else if (IsA(jtnode, JoinExpr)) {
JoinExpr* j = (JoinExpr*)jtnode;
bool need_paren_on_right = false;
need_paren_on_right = PRETTY_PAREN(context) && !IsA(j->rarg, RangeTblRef) &&
!(IsA(j->rarg, JoinExpr) && ((JoinExpr*)j->rarg)->alias != NULL);
if (!PRETTY_PAREN(context) || j->alias != NULL)
appendStringInfoChar(buf, '(');
get_from_clause_item(j->larg, query, context, isNeedError);
if (j->isNatural) {
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
switch (j->jointype) {
case JOIN_INNER:
appendContextKeyword(context, "NATURAL JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
case JOIN_LEFT:
appendContextKeyword(context, "NATURAL LEFT JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
case JOIN_FULL:
appendContextKeyword(context, "NATURAL FULL JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
case JOIN_RIGHT:
appendContextKeyword(context, "NATURAL RIGHT JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized join type: %d", (int)j->jointype)));
}
} else {
switch (j->jointype) {
case JOIN_INNER:
if (j->quals) {
appendContextKeyword(context, " JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
} else if (j->isAsof) {
appendContextKeyword(context, " ASOF JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
} else {
if (j->is_apply_join) {
appendContextKeyword(context, " CROSS APPLY ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 1);
} else {
appendContextKeyword(context, " CROSS JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 1);
}
}
break;
case JOIN_LEFT:
if (j->is_apply_join) {
appendContextKeyword(context, " OUTER APPLY ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
} else {
appendContextKeyword(context, " LEFT JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
}
break;
case JOIN_FULL:
appendContextKeyword(context, " FULL JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
break;
case JOIN_RIGHT:
appendContextKeyword(context, " RIGHT JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
break;
case JOIN_SEMI:
if (context->qrw_phase) {
appendContextKeyword(context, " SEMI JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
break;
}
case JOIN_ANTI:
if (context->qrw_phase) {
appendContextKeyword(context, " ANTI JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 2);
break;
}
case JOIN_LEFT_ANTI_FULL:
if (context->qrw_phase) {
appendContextKeyword(
context, " LEFT ANTI FULL JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
}
case JOIN_RIGHT_ANTI_FULL:
if (context->qrw_phase) {
appendContextKeyword(
context, " RIGHT ANTI FULL JOIN ", -PRETTYINDENT_JOIN, PRETTYINDENT_JOIN, 0);
break;
}
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized join type: %d", (int)j->jointype)));
}
}
if (need_paren_on_right)
appendStringInfoChar(buf, '(');
get_from_clause_item(j->rarg, query, context, isNeedError);
if (need_paren_on_right)
appendStringInfoChar(buf, ')');
context->indentLevel -= PRETTYINDENT_JOIN_ON;
if (!j->isNatural) {
if (j->usingClause) {
ListCell* col = NULL;
appendStringInfo(buf, " USING (");
foreach (col, j->usingClause) {
if (col != list_head(j->usingClause))
appendStringInfo(buf, ", ");
appendStringInfoString(buf, quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
} else if (j->quals) {
appendStringInfo(buf, " ON ");
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr(j->quals, context, false);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
}
}
if (!PRETTY_PAREN(context) || j->alias != NULL)
appendStringInfoChar(buf, ')');
if (j->alias != NULL) {
appendStringInfo(buf, " %s", quote_identifier(j->alias->aliasname));
get_from_clause_alias(j->alias, rt_fetch(j->rtindex, query->rtable), context);
}
} else if (context->qrw_phase && IsA(jtnode, FromExpr)) {
FromExpr* expr = (FromExpr*)jtnode;
appendStringInfoChar(buf, '(');
appendStringInfo(buf, "SELECT *");
get_from_clause(query, " FROM ", context, expr->fromlist);
if (expr->quals != NULL) {
appendContextKeyword(context, " WHERE ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(expr->quals, context, false);
}
appendStringInfoChar(buf, ')');
} else
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized node type: %d", (int)nodeTag(jtnode))));
}
* get_from_clause_alias - reproduce column alias list
*
* This is tricky because we must ignore dropped columns.
*/
static void get_from_clause_alias(Alias* alias, RangeTblEntry* rte, deparse_context* context)
{
StringInfo buf = context->buf;
ListCell* col = NULL;
AttrNumber attnum;
bool first = true;
if (alias == NULL || alias->colnames == NIL)
return;
attnum = 0;
foreach (col, alias->colnames) {
attnum++;
if (get_rte_attribute_is_dropped(rte, attnum))
continue;
if (first) {
appendStringInfoChar(buf, '(');
first = false;
} else
appendStringInfo(buf, ", ");
appendStringInfoString(buf, quote_identifier(strVal(lfirst(col))));
}
if (!first)
appendStringInfoChar(buf, ')');
}
* Description: Print a TableSampleClause.
*
* Parameters: @in tablesample: TableSampleClause node.
* @out context: rewrite sql.
*
* Return: void
*/
static void get_tablesample_def(TableSampleClause* tablesample, deparse_context* context)
{
StringInfo buf = context->buf;
int nargs = 0;
ListCell* l = NULL;
const char* sampleMethod = (tablesample->sampleType == SYSTEM_SAMPLE)
? "SYSTEM"
: ((tablesample->sampleType == BERNOULLI_SAMPLE) ? "BERNOULLI" : "HYBRID");
* We should qualify the handler's function name if it wouldn't be
* resolved by lookup in the current search path.
*/
appendStringInfo(buf, " TABLESAMPLE %s (", sampleMethod);
foreach (l, tablesample->args) {
if (nargs++ > 0) {
appendStringInfoString(buf, ", ");
}
get_rule_expr((Node*)lfirst(l), context, false);
}
appendStringInfoChar(buf, ')');
if (tablesample->repeatable != NULL) {
appendStringInfoString(buf, " REPEATABLE (");
get_rule_expr((Node*)tablesample->repeatable, context, false);
appendStringInfoChar(buf, ')');
}
}
* Description: Print a TimeCapsuleClause.
*
* Parameters:
* @in timecapsule: TimeCapsuleClause node.
* @out context: rewrite sql.
*
* Return: void
*/
static void GetTimecapsuleDef(const TimeCapsuleClause* timeCapsule, deparse_context* context)
{
StringInfo buf = context->buf;
* We should qualify the handler's function name if it wouldn't be
* resolved by lookup in the current search path.
*/
appendStringInfo(buf, " TIMECAPSULE %s ", (timeCapsule->tvtype == TV_VERSION_CSN) ? "CSN" : "TIMESTAMP");
get_rule_expr(timeCapsule->tvver, context, false);
}
* get_from_clause_coldeflist - reproduce FROM clause coldeflist
*
* The coldeflist is appended immediately (no space) to buf. Caller is
* responsible for ensuring that an alias or AS is present before it.
*/
static void get_from_clause_coldeflist(
List* names, List* types, List* typmods, List* collations, deparse_context* context)
{
StringInfo buf = context->buf;
ListCell* l1 = NULL;
ListCell* l2 = NULL;
ListCell* l3 = NULL;
ListCell* l4 = NULL;
int i = 0;
appendStringInfoChar(buf, '(');
l2 = list_head(types);
l3 = list_head(typmods);
l4 = list_head(collations);
foreach (l1, names) {
char* attname = strVal(lfirst(l1));
Oid atttypid;
int32 atttypmod;
Oid attcollation;
atttypid = lfirst_oid(l2);
l2 = lnext(l2);
atttypmod = lfirst_int(l3);
l3 = lnext(l3);
attcollation = lfirst_oid(l4);
l4 = lnext(l4);
if (i > 0)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s %s", quote_identifier(attname), format_type_with_typemod(atttypid, atttypmod));
if (OidIsValid(attcollation) && attcollation != get_typcollation(atttypid))
appendStringInfo(buf, " COLLATE %s", generate_collation_name(attcollation));
i++;
}
appendStringInfoChar(buf, ')');
}
* get_opclass_name - fetch name of an index operator class
*
* The opclass name is appended (after a space) to buf.
*
* Output is suppressed if the opclass is the default for the given
* actual_datatype. (If you don't want this behavior, just pass
* InvalidOid for actual_datatype.)
*/
void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf)
{
HeapTuple ht_opc;
Form_pg_opclass opcrec;
char* opcname = NULL;
char* nspname = NULL;
ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(ht_opc))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for opclass %u", opclass)));
opcrec = (Form_pg_opclass)GETSTRUCT(ht_opc);
if (!OidIsValid(actual_datatype) || GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass) {
opcname = NameStr(opcrec->opcname);
if (OpclassIsVisible(opclass))
appendStringInfo(buf, " %s", quote_identifier(opcname));
else {
nspname = get_namespace_name(opcrec->opcnamespace);
appendStringInfo(buf, " %s.%s", quote_identifier(nspname), quote_identifier(opcname));
}
}
ReleaseSysCache(ht_opc);
}
* processIndirection - take care of array and subfield assignment
*
* We strip any top-level FieldStore or assignment ArrayRef nodes that
* appear in the input, and return the subexpression that's to be assigned.
* If printit is true, we also print out the appropriate decoration for the
* base column name (that the caller just printed).
*/
static Node* processIndirection(Node* node, deparse_context* context, bool printit)
{
StringInfo buf = context->buf;
for (;;) {
if (node == NULL)
break;
if (IsA(node, FieldStore)) {
FieldStore* fstore = (FieldStore*)node;
Oid typrelid;
char* fieldname = NULL;
typrelid = get_typ_typrelid(fstore->resulttype);
if (!OidIsValid(typrelid))
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("argument type %s of FieldStore is not a tuple type",
format_type_be(fstore->resulttype)))));
* Print the field name. There should only be one target field in
* stored rules. There could be more than that in executable
* target lists, but this function cannot be used for that case.
*/
Assert(list_length(fstore->fieldnums) == 1);
fieldname = get_relid_attribute_name(typrelid, linitial_int(fstore->fieldnums));
if (printit)
appendStringInfo(buf, ".%s", quote_identifier(fieldname));
* We ignore arg since it should be an uninteresting reference to
* the target column or subcolumn.
*/
node = (Node*)linitial(fstore->newvals);
} else if (IsA(node, ArrayRef)) {
ArrayRef* aref = (ArrayRef*)node;
if (aref->refassgnexpr == NULL)
break;
if (printit)
printSubscripts(aref, context);
* We ignore refexpr since it should be an uninteresting reference
* to the target column or subcolumn.
*/
node = (Node*)aref->refassgnexpr;
} else
break;
}
return node;
}
static void printSubscripts(ArrayRef* aref, deparse_context* context)
{
StringInfo buf = context->buf;
ListCell* lowlist_item = NULL;
ListCell* uplist_item = NULL;
lowlist_item = list_head(aref->reflowerindexpr);
foreach (uplist_item, aref->refupperindexpr) {
appendStringInfoChar(buf, '[');
if (lowlist_item != NULL) {
get_rule_expr((Node*)lfirst(lowlist_item), context, false);
appendStringInfoChar(buf, ':');
lowlist_item = lnext(lowlist_item);
}
get_rule_expr((Node*)lfirst(uplist_item), context, false);
appendStringInfoChar(buf, ']');
}
}
* quote_identifier - Quote an identifier only if needed
*
* When quotes are needed, we palloc the required space; slightly
* space-wasteful but well worth it for notational simplicity.
*/
const char* quote_identifier(const char* ident)
{
* Can avoid quoting if ident starts with a lowercase letter or underscore
* and contains only lowercase letters, digits, and underscores, *and* is
* not any SQL keyword. Otherwise, supply quotes.
*/
int nquotes = 0;
bool safe = false;
const char* ptr = NULL;
char* result = NULL;
char* optr = NULL;
* would like to use <ctype.h> macros here, but they might yield unwanted
* locale-specific results...
*/
safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
for (ptr = ident; *ptr; ptr++) {
char ch = *ptr;
if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '_')) {
} else {
safe = false;
if (ch == '"')
nquotes++;
}
}
if (u_sess->attr.attr_sql.quote_all_identifiers)
safe = false;
if (safe) {
* Check for keyword. We quote keywords except for unreserved ones.
* (In some cases we could avoid quoting a col_name or type_func_name
* keyword, but it seems much harder than it's worth to tell that.)
*
* Note: ScanKeywordLookup() does case-insensitive comparison, but
* that's fine, since we already know we have all-lower-case.
*/
int kwnum = ScanKeywordLookup(ident, &ScanKeywords);
if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
safe = false;
}
if (safe)
return ident;
result = (char*)palloc(strlen(ident) + nquotes + 2 + 1);
optr = result;
*optr++ = '"';
for (ptr = ident; *ptr; ptr++) {
char ch = *ptr;
if (ch == '"')
*optr++ = '"';
*optr++ = ch;
}
*optr++ = '"';
*optr = '\0';
return result;
}
* quote_qualified_identifier - Quote a possibly-qualified identifier
*
* Return a name of the form qualifier.ident, or just ident if qualifier
* is NULL, quoting each component if necessary. The result is palloc'd.
*/
char* quote_qualified_identifier(const char* qualifier, const char* ident1, const char* ident2)
{
StringInfoData buf;
initStringInfo(&buf);
if (qualifier != NULL)
appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
if (ident1 != NULL)
appendStringInfoString(&buf, quote_identifier(ident1));
if (ident2 != NULL && ident1 != NULL) {
appendStringInfo(&buf, ".%s", quote_identifier(ident2));
} else if (ident1 == NULL && ident2 != NULL) {
appendStringInfoString(&buf, quote_identifier(ident2));
}
return buf.data;
}
* get_relation_name
* Get the unqualified name of a relation specified by OID
*
* This differs from the underlying get_rel_name() function in that it will
* throw error instead of silently returning NULL if the OID is bad.
*/
static char* get_relation_name(Oid relid, bool isNeedError)
{
char* relname = get_rel_name(relid);
if (relname == NULL) {
if (isNeedError) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", relid)));
}
}
return relname;
}
* generate_relation_name
* Compute the name to display for a relation specified by OID
*
* The result includes all necessary quoting and schema-prefixing.
*
* If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
* We will forcibly qualify the relation name if it equals any CTE name
* visible in the namespace list.
*/
static char* generate_relation_name(Oid relid, List* namespaces, bool isNeedError)
{
HeapTuple tp;
Form_pg_class reltup;
bool need_qual = false;
ListCell* nslist = NULL;
char* relname = NULL;
char* nspname = NULL;
char* result = NULL;
tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp)) {
if (isNeedError) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
errmsg("cache lookup failed for relation %u", relid)));
} else {
return NULL;
}
}
reltup = (Form_pg_class)GETSTRUCT(tp);
relname = NameStr(reltup->relname);
foreach (nslist, namespaces) {
deparse_namespace* dpns = (deparse_namespace*)lfirst(nslist);
ListCell* ctlist = NULL;
foreach (ctlist, dpns->ctes) {
CommonTableExpr* cte = (CommonTableExpr*)lfirst(ctlist);
if (strcmp(cte->ctename, relname) == 0) {
need_qual = true;
break;
}
}
if (need_qual)
break;
}
if (!need_qual)
need_qual = !RelationIsVisible(relid);
if (need_qual)
nspname = get_namespace_name(reltup->relnamespace);
else
nspname = NULL;
result = quote_qualified_identifier(nspname, relname);
ReleaseSysCache(tp);
return result;
}
static char* get_function_name_from_depend(Oid funcOid)
{
HeapTuple dependTup = NULL;
Relation dependRel = heap_open(DependRelationId, RowExclusiveLock);
const int keyNum = 2;
ScanKeyData key[keyNum];
bool isnull = true;
Datum depsrc;
char* funcName = NULL;
SysScanDesc scan = NULL;
ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(ProcedureRelationId));
ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(funcOid));
scan = systable_beginscan(dependRel, DependReferenceIndexId, true, NULL, keyNum, key);
while (HeapTupleIsValid(dependTup = systable_getnext(scan))) {
depsrc = heap_getattr(dependTup, Anum_pg_depend_depsrc, RelationGetDescr(dependRel), &isnull);
if (!isnull) {
break;
}
}
if (isnull) {
systable_endscan(scan);
heap_close(dependRel, RowExclusiveLock);
return NULL;
}
funcName = TextDatumGetCString(depsrc);
systable_endscan(scan);
heap_close(dependRel, RowExclusiveLock);
return funcName;
}
* generate_function_name
* Compute the name to display for a function specified by OID,
* given that it is being called with the specified actual arg names and
* types. (Those matter because of ambiguous-function resolution rules.)
*
* The result includes all necessary quoting and schema-prefixing. We can
* also pass back an indication of whether the function is variadic.
*/
static char* generate_function_name(
Oid funcid, int nargs, List* argnames, Oid* argtypes, bool was_variadic, bool* use_variadic_p, bool searchDepend)
{
HeapTuple proctup;
Form_pg_proc procform;
char* proname = NULL;
char* nspname = NULL;
char* result = NULL;
bool use_variadic = false;
FuncDetailCode p_result;
Oid p_funcid;
Oid p_rettype;
bool p_retset = false;
int p_nvargs;
Oid* p_true_typeids = NULL;
Oid p_vatype;
char* pkgname = NULL;
Datum pkgOiddatum;
Oid pkgOid = InvalidOid;
bool isnull = true;
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup)) {
if (searchDepend) {
result = get_function_name_from_depend(funcid);
if (result != NULL) {
return result;
}
}
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcid)));
}
procform = (Form_pg_proc)GETSTRUCT(proctup);
proname = NameStr(procform->proname);
bool ispackage = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_package, &isnull);
pkgOiddatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_packageid, &isnull);
if (!isnull && ispackage) {
pkgOid = DatumGetObjectId(pkgOiddatum);
pkgname = GetPackageName(pkgOid);
}
* If this function's element type of variadic array is not ANY
* or it was used originally, we should print VARIADIC. We must do
* this first since it affects the lookup rules in func_get_detail().
*/
if (use_variadic_p != NULL) {
if (OidIsValid(procform->provariadic)) {
if (procform->provariadic == ANYOID)
use_variadic = was_variadic;
else
use_variadic = true;
}
*use_variadic_p = use_variadic;
} else {
Assert(!was_variadic);
}
* The idea here is to schema-qualify only if the parser would fail to
* resolve the correct function given the unqualified func name with the
* specified argtypes and VARIADIC flag.
*/
p_result = func_get_detail(list_make1(makeString(proname)),
NIL,
argnames,
nargs,
argtypes,
!use_variadic,
true,
&p_funcid,
&p_rettype,
&p_retset,
&p_nvargs,
&p_vatype,
&p_true_typeids,
NULL);
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE || p_result == FUNCDETAIL_WINDOWFUNC) &&
p_funcid == funcid)
nspname = NULL;
else
nspname = get_namespace_name(procform->pronamespace);
if (OidIsValid(pkgOid)) {
result = quote_qualified_identifier(nspname, pkgname, proname);
} else {
result = quote_qualified_identifier(nspname, proname);
}
ReleaseSysCache(proctup);
return result;
}
static Operator GetOperator(Form_pg_operator operform, Oid arg1, Oid arg2)
{
Operator p_result;
char* oprname = NameStr(operform->oprname);
Value* oprnameStr = makeString(oprname);
List* oprnameList = list_make1(oprnameStr);
* The idea here is to schema-qualify only if the parser would fail to
* resolve the correct operator given the unqualified op name with the
* specified argtypes.
*/
switch (operform->oprkind) {
case 'b':
p_result = oper(NULL, oprnameList, arg1, arg2, true, -1);
break;
case 'l':
p_result = left_oper(NULL, oprnameList, arg2, true, -1);
break;
case 'r':
p_result = right_oper(NULL, oprnameList, arg1, true, -1);
break;
default:
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized oprkind: %d", operform->oprkind))));
p_result = NULL;
break;
}
list_free(oprnameList);
pfree(oprnameStr);
return p_result;
}
* generate_operator_name
* Compute the name to display for an operator specified by OID,
* given that it is being called with the specified actual arg types.
* (Arg types matter because of ambiguous-operator resolution rules.
* Pass InvalidOid for unused arg of a unary operator.)
*
* The result includes all necessary quoting and schema-prefixing,
* plus the OPERATOR() decoration needed to use a qualified operator name
* in an expression.
*/
static char* generate_operator_name(Oid operid, Oid arg1, Oid arg2)
{
StringInfoData buf;
HeapTuple opertup;
Form_pg_operator operform;
char* oprname = NULL;
char* nspname = NULL;
Operator p_result;
initStringInfo(&buf);
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
if (!HeapTupleIsValid(opertup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for operator %u", operid)));
operform = (Form_pg_operator)GETSTRUCT(opertup);
oprname = NameStr(operform->oprname);
p_result = GetOperator(operform, arg1, arg2);
if (p_result != NULL && oprid(p_result) == operid)
nspname = NULL;
else {
nspname = get_namespace_name(operform->oprnamespace);
appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
}
appendStringInfoString(&buf, oprname);
if (nspname != NULL)
appendStringInfoChar(&buf, ')');
if (p_result != NULL)
ReleaseSysCache(p_result);
ReleaseSysCache(opertup);
return buf.data;
}
* generate_collation_name
* Compute the name to display for a collation specified by OID
*
* The result includes all necessary quoting and schema-prefixing.
*/
char* generate_collation_name(Oid collid)
{
HeapTuple tp;
Form_pg_collation colltup;
char* collname = NULL;
char* nspname = NULL;
char* result = NULL;
tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
if (!HeapTupleIsValid(tp))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for collation %u", collid)));
colltup = (Form_pg_collation)GETSTRUCT(tp);
collname = NameStr(colltup->collname);
if (!CollationIsVisible(collid))
nspname = get_namespace_name(colltup->collnamespace);
else
nspname = NULL;
result = quote_qualified_identifier(nspname, collname);
ReleaseSysCache(tp);
return result;
}
* Given a C string, produce a TEXT datum.
*
* We assume that the input was palloc'd and may be freed.
*/
static text* string_to_text(char* str)
{
text* result = NULL;
result = cstring_to_text(str);
pfree_ext(str);
return result;
}
* Generate a C string representing a relation's reloptions, or NULL if none.
*/
char* flatten_reloptions(Oid relid)
{
char* result = NULL;
HeapTuple tuple;
Datum reloptions;
bool isnull = false;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
if (!isnull) {
Datum sep, txt;
* We want to use array_to_text(reloptions, ', ') --- but
* DirectFunctionCall2(array_to_text) does not work, because
* array_to_text() relies on flinfo to be valid. So use
* OidFunctionCall2.
*/
sep = CStringGetTextDatum(", ");
txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
result = TextDatumGetCString(txt);
}
ReleaseSysCache(tuple);
return result;
}
Datum getDistributeKey(PG_FUNCTION_ARGS)
{
Oid relOid = PG_GETARG_OID(0);
Relation pcrel;
ScanKeyData skey;
SysScanDesc pcscan;
HeapTuple htup;
Form_pgxc_class pgxc_class;
StringInfoData attname = {NULL, 0, 0, 0};
initStringInfo(&attname);
ScanKeyInit(&skey, Anum_pgxc_class_pcrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid));
pcrel = heap_open(PgxcClassRelationId, AccessShareLock);
pcscan = systable_beginscan(pcrel, PgxcClassPgxcRelIdIndexId, true, NULL, 1, &skey);
htup = systable_getnext(pcscan);
if (!HeapTupleIsValid(htup)) {
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
if (IS_PGXC_COORDINATOR) {
ereport(ERROR,
(errmodule(MOD_OPT),
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not open relation with OID %u", relOid))));
} else if (IS_SINGLE_NODE) {
PG_RETURN_NULL();
} else {
ereport(WARNING,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("The info of distribution for the table is not available due to dedicated connection to "
"datanode")));
PG_RETURN_NULL();
}
}
pgxc_class = (Form_pgxc_class)GETSTRUCT(htup);
if (pgxc_class->pcattnum.values[0] == InvalidAttrNumber) {
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
PG_RETURN_NULL();
}
for (int i = 0; i < pgxc_class->pcattnum.dim1; i++) {
if (i == 0) {
appendStringInfoString(&attname, get_attname(relOid, pgxc_class->pcattnum.values[i]));
} else {
appendStringInfoString(&attname, ", ");
appendStringInfoString(&attname, get_attname(relOid, pgxc_class->pcattnum.values[i]));
}
}
systable_endscan(pcscan);
heap_close(pcrel, AccessShareLock);
PG_RETURN_TEXT_P(string_to_text(attname.data));
}
List* get_operator_name(Oid operid, Oid arg1, Oid arg2)
{
HeapTuple opertup;
Form_pg_operator operform;
char* oprname = NULL;
char* nspname = NULL;
Operator p_result;
List* opname = NIL;
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
if (!HeapTupleIsValid(opertup))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for operator %u", operid)));
operform = (Form_pg_operator)GETSTRUCT(opertup);
oprname = pstrdup(NameStr(operform->oprname));
p_result = GetOperator(operform, arg1, arg2);
if (p_result != NULL && oprid(p_result) == operid) {
opname = NULL;
opname = list_make1(makeString(oprname));
} else {
nspname = get_namespace_name(operform->oprnamespace);
opname = list_make2(makeString(nspname), makeString(oprname));
}
if (p_result)
ReleaseSysCache(p_result);
ReleaseSysCache(opertup);
return opname;
}
static void deparse_sequence_options(List* options, StringInfoData& str, bool owned_by_none, bool doingAlter)
{
ListCell* option = NULL;
foreach (option, options) {
DefElem* defel = (DefElem*)lfirst(option);
if (strcmp(defel->defname, "increment") == 0) {
appendStringInfo(&str, " INCREMENT %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "start") == 0) {
appendStringInfo(&str, " START %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "restart") == 0) {
if (defel->arg == NULL)
appendStringInfo(&str, " RESTART");
else
appendStringInfo(&str, " RESTART %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "maxvalue") == 0) {
if (defel->arg == NULL)
appendStringInfo(&str, " NOMAXVALUE");
else
appendStringInfo(&str, " MAXVALUE %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "minvalue") == 0) {
if (defel->arg == NULL)
appendStringInfo(&str, " NOMINVALUE");
else
appendStringInfo(&str, " MINVALUE %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "cache") == 0) {
appendStringInfo(&str, " CACHE %ld", defGetInt64(defel));
} else if (strcmp(defel->defname, "cycle") == 0) {
if (defGetInt64(defel) != 0)
appendStringInfo(&str, " CYCLE");
else
appendStringInfo(&str, " NO CYCLE");
} else if (strcmp(defel->defname, "owned_by") == 0) {
if (owned_by_none) {
if (doingAlter) {
appendStringInfo(&str, " OWNED BY NONE");
}
continue;
}
List* owned_by = defGetQualifiedName(defel);
int nnames = list_length(owned_by);
if (list_length(owned_by) == 1) {
appendStringInfo(&str, " OWNED BY NONE");
} else {
List* relname = NIL;
char* attrname = NULL;
RangeVar* rel = NULL;
relname = list_truncate(list_copy(owned_by), nnames - 1);
attrname = strVal(lfirst(list_tail(owned_by)));
rel = makeRangeVarFromNameList(relname);
appendStringInfo(&str, " OWNED BY ");
if (rel->schemaname && rel->schemaname[0])
appendStringInfo(&str, "%s.", quote_identifier(rel->schemaname));
appendStringInfo(&str, "%s.%s", quote_identifier(rel->relname), quote_identifier(attrname));
}
} else {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("option \"%s\" not recognized", defel->defname)));
}
}
}
* deparse_create_sequence
* - General utility for deparsing create sequence.
*
*/
char* deparse_create_sequence(Node* stmt, bool owned_by_none)
{
StringInfoData str;
CreateSeqStmt* create_seq = (CreateSeqStmt*)stmt;
RangeVar* seqname = create_seq->sequence;
bool istemp = (seqname->relpersistence == RELPERSISTENCE_TEMP);
initStringInfo(&str);
appendStringInfo(&str, "CREATE %s SEQUENCE ", istemp ? "TEMP" : "");
if (seqname->schemaname && seqname->schemaname[0])
appendStringInfo(&str, "%s.", quote_identifier(seqname->schemaname));
appendStringInfo(&str, "%s", quote_identifier(seqname->relname));
deparse_sequence_options(create_seq->options, str, owned_by_none, false);
appendStringInfo(&str, ";");
return str.data;
}
* deparse_alter_sequence
* - General utility for deparsing alter sequence.
*
*/
char* deparse_alter_sequence(Node* stmt, bool owned_by_none)
{
StringInfoData str;
AlterSeqStmt* alter_seq = (AlterSeqStmt*)stmt;
RangeVar* seqname = alter_seq->sequence;
bool missing_ok = alter_seq->missing_ok;
initStringInfo(&str);
appendStringInfo(&str, "ALTER %s SEQUENCE ", missing_ok ? "IF EXISTS" : "");
if (seqname->schemaname && seqname->schemaname[0])
appendStringInfo(&str, "%s.", quote_identifier(seqname->schemaname));
appendStringInfo(&str, "%s", quote_identifier(seqname->relname));
deparse_sequence_options(alter_seq->options, str, owned_by_none, true);
appendStringInfo(&str, ";");
return str.data;
}
* Replace column managed type with original type for proper print
* via proallargtypes
*/
static void replace_cl_types_in_argtypes(Oid func_id, int numargs, Oid* argtypes, bool* is_client_logic)
{
HeapTuple gs_oldtup = NULL;
Oid* vec_gs_all_types_orig = NULL;
oidvector* proargcachedcol = NULL;
int n_gs_args = 0;
bool use_all_arg_types = false;
for (int i = 0; i < numargs; i++) {
if (IsClientLogicType(argtypes[i])) {
if (!HeapTupleIsValid(gs_oldtup)) {
* check if use proallargtypes for substitution
*/
gs_oldtup = SearchSysCache1(GSCLPROCID, ObjectIdGetDatum(func_id));
if (!HeapTupleIsValid(gs_oldtup)) {
break;
}
bool isnull = false;
Datum gs_all_types_orig =
SysCacheGetAttr(GSCLPROCID, gs_oldtup, Anum_gs_encrypted_proc_proallargtypes_orig, &isnull);
if (!isnull) {
ArrayType* arr_gs_all_types_orig = DatumGetArrayTypeP(gs_all_types_orig);
vec_gs_all_types_orig = (Oid*)ARR_DATA_PTR(arr_gs_all_types_orig);
n_gs_args = ARR_DIMS(arr_gs_all_types_orig)[0];
if (n_gs_args <= numargs) {
use_all_arg_types = true;
}
}
if (!use_all_arg_types) {
proargcachedcol = (oidvector*)DatumGetPointer(
SysCacheGetAttr(GSCLPROCID, gs_oldtup, Anum_gs_encrypted_proc_proargcachedcol, &isnull));
n_gs_args = proargcachedcol->dim1;
}
}
if (i >= n_gs_args) {
break;
}
if (use_all_arg_types) {
* For output parameter if origin type is -1
* use return value instead
*/
if (vec_gs_all_types_orig[i] != (Oid)-1) {
argtypes[i] = vec_gs_all_types_orig[i];
} else {
bool isnull = false;
Datum rettype_orig =
SysCacheGetAttr(GSCLPROCID, gs_oldtup, Anum_gs_encrypted_proc_prorettype_orig, &isnull);
if (!isnull) {
argtypes[i] = DatumGetObjectId(rettype_orig);
}
}
} else {
Oid cachedColId = proargcachedcol->values[i];
HeapTuple tup = SearchSysCache1(CEOID, ObjectIdGetDatum(cachedColId));
if (HeapTupleIsValid(tup)) {
Form_gs_encrypted_columns gs_cl_columns = (Form_gs_encrypted_columns)GETSTRUCT(tup);
argtypes[i] = gs_cl_columns->data_type_original_oid;
ReleaseSysCache(tup);
}
}
is_client_logic[i] = true;
}
}
if (HeapTupleIsValid(gs_oldtup)) {
ReleaseSysCache(gs_oldtup);
}
}
Oid pg_get_serial_sequence_oid(text* tablename, text* columnname)
{
RangeVar* tablerv = NULL;
Oid tableOid;
char* column = NULL;
AttrNumber attnum;
Oid sequenceId = InvalidOid;
Relation depRel;
ScanKeyData key[3];
SysScanDesc scan;
HeapTuple tup;
List* names = NIL;
names = textToQualifiedNameList(tablename);
tablerv = makeRangeVarFromNameList(names);
tableOid = RangeVarGetRelid(tablerv, NoLock, false);
column = text_to_cstring(columnname);
attnum = get_attnum(tableOid, column);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist", column, tablerv->relname)));
depRel = heap_open(DependRelationId, AccessShareLock);
ScanKeyInit(
&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(tableOid));
ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 3, key);
while (HeapTupleIsValid(tup = systable_getnext(scan))) {
Form_pg_depend deprec = (Form_pg_depend)GETSTRUCT(tup);
if (deprec->classid == RelationRelationId && deprec->objsubid == 0 && deprec->deptype == DEPENDENCY_AUTO &&
get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE) {
sequenceId = deprec->objid;
break;
}
}
systable_endscan(scan);
heap_close(depRel, AccessShareLock);
if (OidIsValid(sequenceId)) {
HeapTuple classtup;
Form_pg_class classtuple;
char* nspname = NULL;
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
if (!HeapTupleIsValid(classtup))
return InvalidOid;
classtuple = (Form_pg_class)GETSTRUCT(classtup);
nspname = get_namespace_name(classtuple->relnamespace);
if (nspname == NULL) {
ReleaseSysCache(classtup);
return InvalidOid;
}
ReleaseSysCache(classtup);
return sequenceId;
}
return InvalidOid;
}