blob: f82da5e9743aea2d0a6684a86354fb3ef938f704 [file] [log] [blame]
/**********************************************************************
* ruleutils.c - Functions to convert stored expressions/querytrees
* back to source text
*
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.235.2.5 2008/05/03 23:19:33 tgl Exp $
**********************************************************************/
#include "postgres.h"
#include <unistd.h>
#include <fcntl.h>
#include "access/genam.h"
#include "access/sysattr.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_attribute_encoding.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_trigger.h"
#include "cdb/cdbpartition.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/gramparse.h"
#include "parser/keywords.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_cte.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
/* ----------
* Pretty formatting constants
* ----------
*/
/* Indent counts */
#define PRETTYINDENT_STD 8
#define PRETTYINDENT_JOIN 13
#define PRETTYINDENT_JOIN_ON (PRETTYINDENT_JOIN-PRETTYINDENT_STD)
#define PRETTYINDENT_VAR 4
/* Pretty flags */
#define PRETTYFLAG_PAREN 1
#define PRETTYFLAG_INDENT 2
/* macro to test if pretty action needed */
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
/* ----------
* Local data types
* ----------
*/
/* Context info needed for invoking a recursive querytree display routine */
typedef struct
{
StringInfo buf; /* output buffer to append to */
List *namespaces; /* List of deparse_namespace nodes */
int prettyFlags; /* enabling of pretty-print functions */
int indentLevel; /* current indent level for prettyprint */
bool varprefix; /* TRUE to print prefixes on Vars */
Query *query;
} 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.
*
* For deparsing plan trees, we allow two special RTE entries that are not
* part of the rtable list (partly because they don't have consecutively
* allocated varnos).
*/
typedef struct
{
List *rtable; /* List of RangeTblEntry nodes */
List *ctes; /* List of CommonTableExpr nodes */
int outer_varno; /* varno for outer_rte */
RangeTblEntry *outer_rte; /* special RangeTblEntry, or NULL */
int inner_varno; /* varno for inner_rte */
RangeTblEntry *inner_rte; /* special RangeTblEntry, or NULL */
} deparse_namespace;
/* ----------
* Global data
* ----------
*/
static SPIPlanPtr plan_getrulebyoid = NULL;
static const char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
static SPIPlanPtr plan_getviewrule = NULL;
static const char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
/* ----------
* 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);
static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
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, bool showTblSpc,
int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static char *pg_get_expr_worker(text *expr, Oid relid, char *relname,
int prettyFlags);
static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags);
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags);
static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
TupleDesc resultDesc, int prettyFlags, int startIndent);
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_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(List *targetList, deparse_context *context,
TupleDesc resultDesc);
static void get_setop_query(Node *setOp, Query *query,
deparse_context *context,
TupleDesc resultDesc);
static void get_rule_grouplist(List *grplist, List *tlist,
bool in_grpsets, deparse_context *context);
static void get_rule_groupingclause(GroupingClause *grp, List *tlist,
deparse_context *context);
static Node *get_rule_sortgroupclause(SortClause *srt, List *tlist,
bool force_colno,
deparse_context *context);
static void get_names_for_var(Var *var, int levelsup, deparse_context *context,
const char **schemaname,
const char **refname,
const char **attname);
static RangeTblEntry *find_rte_by_refname(const char *refname,
deparse_context *context);
static const char *get_simple_binary_op_name(OpExpr *expr);
static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
static void appendStringInfoSpaces(StringInfo buf, int count);
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);
static void get_oper_expr(OpExpr *expr, deparse_context *context);
static void get_func_expr(FuncExpr *expr, deparse_context *context,
bool showimplicit);
static void get_groupingfunc_expr(GroupingFunc *grpfunc,
deparse_context *context);
static void get_agg_expr(Aggref *aggref, deparse_context *context);
static void get_windowedge_expr(WindowFrameEdge *edge,
deparse_context *context);
static void get_sortlist_expr(List *l, List *targetList, bool force_colno,
deparse_context *context, char *keyword_clause);
static void get_windowspec_expr(WindowSpec *spec, deparse_context *context);
static void get_windowref_expr(WindowRef *wref, deparse_context *context);
static void get_const_expr(Const *constval, deparse_context *context,
int showtype);
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
static void get_from_clause(Query *query, const char *prefix,
deparse_context *context);
static void get_from_clause_item(Node *jtnode, Query *query,
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,
deparse_context *context);
static 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 *generate_relation_name(Oid relid, List *namespaces);
static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
static void get_partition_recursive(PartitionNode *pn,
deparse_context *head,
deparse_context *body,
int16 *leveldone,
int bLeafTablename);
#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
*/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "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 (plan_getrulebyoid == NULL)
{
Oid argtypes[1];
SPIPlanPtr plan;
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
plan_getrulebyoid = SPI_saveplan(plan);
}
/*
* Get the pg_rewrite tuple for this rule
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "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
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
return buf.data;
}
/* ----------
* get_viewdef - Mainly the same thing, but we
* only return the SELECT part of a view
* ----------
*/
Datum
pg_get_viewdef(PG_FUNCTION_ARGS)
{
/* By OID */
Oid viewoid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
}
Datum
pg_get_viewdef_ext(PG_FUNCTION_ARGS)
{
/* By OID */
Oid viewoid = 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_viewdef_worker(viewoid, prettyFlags)));
}
Datum
pg_get_viewdef_name(PG_FUNCTION_ARGS)
{
/* By qualified name */
text *viewname = PG_GETARG_TEXT_P(0);
RangeVar *viewrel;
Oid viewoid;
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
viewoid = RangeVarGetRelid(viewrel, false, false /*allowHcatalog*/);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
}
Datum
pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
{
/* By qualified name */
text *viewname = PG_GETARG_TEXT_P(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
RangeVar *viewrel;
Oid viewoid;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
viewoid = RangeVarGetRelid(viewrel, false, false /*allowHcatalog*/);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
}
/*
* Common code for by-OID and by-name variants of pg_get_viewdef
*/
static char *
pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
{
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
*/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "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 (plan_getviewrule == NULL)
{
Oid argtypes[2];
SPIPlanPtr plan;
argtypes[0] = OIDOID;
argtypes[1] = NAMEOID;
plan = SPI_prepare(query_getviewrule, 2, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
plan_getviewrule = SPI_saveplan(plan);
}
/*
* Get the pg_rewrite tuple for the view's SELECT rule
*/
args[0] = ObjectIdGetDatum(viewoid);
args[1] = PointerGetDatum(ViewSelectRuleName);
nulls[0] = ' ';
nulls[1] = ' ';
spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "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);
}
/*
* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
return buf.data;
}
/* ----------
* get_triggerdef - Get the definition of a trigger
* ----------
*/
Datum
pg_get_triggerdef(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
HeapTuple ht_trig;
Form_pg_trigger trigrec;
StringInfoData buf;
Relation tgrel;
cqContext cqc;
int findx = 0;
char *tgname;
/*
* Fetch the pg_trigger tuple by the Oid of the trigger
*/
tgrel = heap_open(TriggerRelationId, AccessShareLock);
ht_trig = caql_getfirst(
caql_addrel(cqclr(&cqc), tgrel),
cql("SELECT * FROM pg_trigger "
" WHERE oid = :1 ",
ObjectIdGetDatum(trigid)));
if (!HeapTupleIsValid(ht_trig))
elog(ERROR, "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);
appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
trigrec->tgisconstraint ? "CONSTRAINT " : "",
quote_identifier(tgname));
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
else
appendStringInfo(&buf, "AFTER");
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");
}
appendStringInfo(&buf, " ON %s ",
generate_relation_name(trigrec->tgrelid, NIL));
if (trigrec->tgisconstraint)
{
if (trigrec->tgconstrrelid != InvalidOid)
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 ");
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0, NULL));
if (trigrec->tgnargs > 0)
{
bytea *val;
bool isnull;
char *p;
int i;
val = DatumGetByteaP(fastgetattr(ht_trig,
Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull));
if (isnull)
elog(ERROR, "tgargs is null for trigger %u", trigid);
p = (char *) VARDATA(val);
for (i = 0; i < trigrec->tgnargs; i++)
{
if (i > 0)
appendStringInfo(&buf, ", ");
/*
* 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, '\'');
while (*p)
{
char ch = *p++;
if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
appendStringInfoChar(&buf, ch);
appendStringInfoChar(&buf, ch);
}
appendStringInfoChar(&buf, '\'');
/* advance p to next string embedded in tgargs */
p++;
}
}
/* We deliberately do not put semi-colon at end */
appendStringInfo(&buf, ")");
/* Clean up */
heap_close(tgrel, AccessShareLock);
PG_RETURN_TEXT_P(string_to_text(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 index tablespace if not default.
* ----------
*/
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,
false, 0)));
}
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,
false, prettyFlags)));
}
/* Internal version that returns a palloc'd C string */
char *
pg_get_indexdef_string(Oid indexrelid)
{
return pg_get_indexdef_worker(indexrelid, 0, true, 0);
}
static char *
pg_get_indexdef_worker(Oid indexrelid, int colno, bool showTblSpc,
int prettyFlags)
{
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
Form_pg_index idxrec;
Form_pg_class idxrelrec;
Form_pg_am amrec;
List *indexprs;
ListCell *indexpr_item;
List *context;
Oid indrelid;
int keyno;
Oid keycoltype;
Datum indclassDatum;
bool isnull;
oidvector *indclass;
StringInfoData buf;
char *str;
char *sep;
cqContext *idxcqCtx;
cqContext *irlcqCtx;
cqContext *amcqCtx;
/*
* Fetch the pg_index tuple by the Oid of the index
*/
idxcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_index "
" WHERE indexrelid = :1 ",
ObjectIdGetDatum(indexrelid)));
ht_idx = caql_getnext(idxcqCtx);
if (!HeapTupleIsValid(ht_idx))
{
/* Was: elog(ERROR, "cache lookup failed for index %u", indexrelid); */
/* See: MPP-10387. */
return pstrdup("Not an index");
}
idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
indrelid = idxrec->indrelid;
Assert(indexrelid == idxrec->indexrelid);
/* Must get indclass the hard way */
indclassDatum = caql_getattr(idxcqCtx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
/*
* Fetch the pg_class tuple of the index relation
*/
irlcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(indexrelid)));
ht_idxrel = caql_getnext(irlcqCtx);
if (!HeapTupleIsValid(ht_idxrel))
elog(ERROR, "cache lookup failed for relation %u", indexrelid);
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
/*
* Fetch the pg_am tuple of the index' access method
*/
amcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_am "
" WHERE oid = :1 ",
ObjectIdGetDatum(idxrelrec->relam)));
ht_am = caql_getnext(amcqCtx);
if (!HeapTupleIsValid(ht_am))
elog(ERROR, "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))
{
Datum exprsDatum;
bool isnull;
char *exprsString;
exprsDatum = caql_getattr(idxcqCtx,
Anum_pg_index_indexprs, &isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
indexprs = (List *) stringToNode(exprsString);
pfree(exprsString);
}
else
indexprs = NIL;
indexpr_item = list_head(indexprs);
context = deparse_context_for(get_rel_name(indrelid), indrelid);
/*
* 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 (!colno)
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)));
/*
* Report the indexed attributes
*/
sep = "";
for (keyno = 0; keyno < idxrec->indnatts; keyno++)
{
AttrNumber attnum = idxrec->indkey.values[keyno];
if (!colno)
appendStringInfoString(&buf, sep);
sep = ", ";
if (attnum != 0)
{
/* Simple index column */
char *attname;
attname = get_relid_attribute_name(indrelid, attnum);
if (!colno || colno == keyno + 1)
appendStringInfoString(&buf, quote_identifier(attname));
keycoltype = get_atttype(indrelid, attnum);
}
else
{
/* expressional index */
Node *indexkey;
if (indexpr_item == NULL)
elog(ERROR, "too few entries in indexprs list");
indexkey = (Node *) lfirst(indexpr_item);
indexpr_item = lnext(indexpr_item);
/* Deparse */
str = deparse_expression_pretty(indexkey, context, false, false,
prettyFlags, 0);
if (!colno || colno == keyno + 1)
{
/* Need parens if it's not a bare function call */
if (indexkey && IsA(indexkey, FuncExpr) &&
((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
appendStringInfoString(&buf, str);
else
appendStringInfo(&buf, "(%s)", str);
}
keycoltype = exprType(indexkey);
}
/*
* Add the operator class name
*/
if (!colno)
get_opclass_name(indclass->values[keyno], keycoltype,
&buf);
}
if (!colno)
{
appendStringInfoChar(&buf, ')');
/*
* If it has options, append "WITH (options)"
*/
str = flatten_reloptions(indexrelid);
if (str)
{
appendStringInfo(&buf, " WITH (%s)", str);
pfree(str);
}
/*
* If it's in a nondefault tablespace, say so, but only if requested
*/
if (showTblSpc)
{
Oid tblspc;
tblspc = get_rel_tablespace(indexrelid);
if (OidIsValid(tblspc))
appendStringInfo(&buf, " TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
}
/*
* If it's a partial index, decompile and append the predicate
*/
if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
{
Node *node;
Datum predDatum;
bool isnull;
char *predString;
/* Convert text string to node tree */
predDatum = caql_getattr(idxcqCtx,
Anum_pg_index_indpred, &isnull);
Assert(!isnull);
predString = TextDatumGetCString(predDatum);
node = (Node *) stringToNode(predString);
pfree(predString);
/* Deparse */
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
appendStringInfo(&buf, " WHERE %s", str);
}
}
/* Clean up */
caql_endscan(idxcqCtx);
caql_endscan(irlcqCtx);
caql_endscan(amcqCtx);
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)));
}
/* Internal version that returns a palloc'd C string */
char *
pg_get_constraintdef_string(Oid constraintId)
{
return pg_get_constraintdef_worker(constraintId, true, 0);
}
/* Internal version that returns a palloc'd C string */
char *
pg_get_constraintexpr_string(Oid constraintId)
{
return pg_get_constraintdef_worker(constraintId, false, 0);
}
static char *
pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags)
{
StringInfoData buf;
Relation conDesc;
cqContext cqc;
HeapTuple tup;
Form_pg_constraint conForm;
/*
* Fetch the pg_constraint row. There's no syscache for pg_constraint so
* we must do it the hard way.
*/
conDesc = heap_open(ConstraintRelationId, AccessShareLock);
tup = caql_getfirst(
caql_addrel(cqclr(&cqc), conDesc),
cql("SELECT * FROM pg_constraint "
" WHERE oid = :1 ",
ObjectIdGetDatum(constraintId)));
if (!HeapTupleIsValid(tup))
elog(ERROR, "could not find tuple 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;
const char *string;
/* Start off the constraint definition */
appendStringInfo(&buf, "FOREIGN KEY (");
/* Fetch and build referencing-column list */
val = heap_getattr(tup, Anum_pg_constraint_conkey,
RelationGetDescr(conDesc), &isnull);
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
decompile_column_index_array(val, conForm->conrelid, &buf);
/* add foreign relation name */
appendStringInfo(&buf, ") REFERENCES %s(",
generate_relation_name(conForm->confrelid, NIL));
/* Fetch and build referenced-column list */
val = heap_getattr(tup, Anum_pg_constraint_confkey,
RelationGetDescr(conDesc), &isnull);
if (isnull)
elog(ERROR, "null confkey for constraint %u",
constraintId);
decompile_column_index_array(val, conForm->confrelid, &buf);
appendStringInfo(&buf, ")");
/* Add match type */
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:
elog(ERROR, "unrecognized confmatchtype: %d",
conForm->confmatchtype);
string = ""; /* keep compiler quiet */
break;
}
appendStringInfoString(&buf, string);
/* Add ON UPDATE and ON DELETE clauses, if needed */
switch (conForm->confupdtype)
{
case FKCONSTR_ACTION_NOACTION:
string = NULL; /* suppress default */
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:
elog(ERROR, "unrecognized confupdtype: %d",
conForm->confupdtype);
string = NULL; /* keep compiler quiet */
break;
}
if (string)
appendStringInfo(&buf, " ON UPDATE %s", string);
switch (conForm->confdeltype)
{
case FKCONSTR_ACTION_NOACTION:
string = NULL; /* suppress default */
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:
elog(ERROR, "unrecognized confdeltype: %d",
conForm->confdeltype);
string = NULL; /* keep compiler quiet */
break;
}
if (string)
appendStringInfo(&buf, " ON DELETE %s", string);
if (conForm->condeferrable)
appendStringInfo(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(&buf, " INITIALLY DEFERRED");
break;
}
case CONSTRAINT_PRIMARY:
case CONSTRAINT_UNIQUE:
{
Datum val;
bool isnull;
Oid indexId;
/* Start off the constraint definition */
if (conForm->contype == CONSTRAINT_PRIMARY)
appendStringInfo(&buf, "PRIMARY KEY (");
else
appendStringInfo(&buf, "UNIQUE (");
/* Fetch and build target column list */
val = heap_getattr(tup, Anum_pg_constraint_conkey,
RelationGetDescr(conDesc), &isnull);
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
decompile_column_index_array(val, conForm->conrelid, &buf);
appendStringInfo(&buf, ")");
indexId = get_constraint_index(constraintId);
/* XXX why do we only print these bits if fullCommand? */
if (fullCommand && OidIsValid(indexId))
{
char *options = flatten_reloptions(indexId);
Oid tblspc;
if (options)
{
appendStringInfo(&buf, " WITH (%s)", options);
pfree(options);
}
tblspc = get_rel_tablespace(indexId);
if (OidIsValid(tblspc))
appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
}
break;
}
case CONSTRAINT_CHECK:
{
Datum val;
bool isnull;
char *conbin;
char *consrc;
Node *expr;
List *context;
/* Fetch constraint expression in parsetree form */
val = heap_getattr(tup, Anum_pg_constraint_conbin,
RelationGetDescr(conDesc), &isnull);
if (isnull)
elog(ERROR, "null conbin for constraint %u",
constraintId);
conbin = DatumGetCString(DirectFunctionCall1(textout, val));
expr = stringToNode(conbin);
/* Set up deparsing context for Var nodes in constraint */
if (conForm->conrelid != InvalidOid)
{
/* relation constraint */
context = deparse_context_for(get_rel_name(conForm->conrelid),
conForm->conrelid);
}
else
{
/* domain constraint --- can't have Vars */
context = NIL;
}
consrc = deparse_expression_pretty(expr, context, false, false,
prettyFlags, 0);
/*
* Now emit the constraint definition. 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)", consrc);
break;
}
default:
elog(ERROR, "invalid constraint type \"%c\"", conForm->contype);
break;
}
/* Cleanup */
heap_close(conDesc, AccessShareLock);
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;
int nKeys;
int j;
/* Extract data from array of int16 */
deconstruct_array(DatumGetArrayTypeP(column_index_array),
INT2OID, 2, true, 's',
&keys, NULL, &nKeys);
for (j = 0; j < nKeys; j++)
{
char *colName;
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.
* ----------
*/
Datum
pg_get_expr(PG_FUNCTION_ARGS)
{
text *expr = PG_GETARG_TEXT_P(0);
Oid relid = PG_GETARG_OID(1);
char *relname;
/* Get the name for the relation */
relname = get_rel_name(relid);
if (relname == NULL)
PG_RETURN_NULL(); /* should we raise an error? */
PG_RETURN_TEXT_P(string_to_text(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;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
/* Get the name for the relation */
relname = get_rel_name(relid);
if (relname == NULL)
PG_RETURN_NULL(); /* should we raise an error? */
PG_RETURN_TEXT_P(string_to_text(pg_get_expr_worker(expr, relid, relname, prettyFlags)));
}
static char *
pg_get_expr_worker(text *expr, Oid relid, char *relname, int prettyFlags)
{
Node *node;
List *context;
char *exprstr;
char *str;
/* Convert input TEXT object to C string */
exprstr = text_to_cstring(expr);
/* Convert expression to node tree */
node = (Node *) stringToNode(exprstr);
pfree(exprstr);
/* Prepare deparse context if needed */
if (OidIsValid(relid))
context = deparse_context_for(relname, relid);
else
context = NIL;
/* Deparse */
context = deparse_context_for(relname, relid);
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
return str;
}
/* ----------
* 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;
int fetchCount;
char *rname = NULL;
/*
* Allocate space for the result
*/
result = (Name) palloc(NAMEDATALEN);
memset(NameStr(*result), 0, NAMEDATALEN);
/*
* Get the pg_authid entry and print the result
*/
rname = caql_getcstring_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT rolname FROM pg_authid "
" WHERE oid = :1 ",
ObjectIdGetDatum(roleid)));
if (fetchCount)
{
StrNCpy(NameStr(*result), rname, NAMEDATALEN);
pfree(rname);
}
else
sprintf(NameStr(*result), "unknown (OID=%u)", roleid);
PG_RETURN_NAME(result);
}
/*
* 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_P(1);
RangeVar *tablerv;
Oid tableOid;
char *column;
AttrNumber attnum;
Oid sequenceId = InvalidOid;
cqContext *pcqCtx;
HeapTuple tup;
/* Get the OID of the table */
tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
tableOid = RangeVarGetRelid(tablerv, false, false /*allowHcatalog*/);
/* Get the number of the column */
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)));
/* Search the dependency table for the dependent sequence */
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_depend "
" WHERE refclassid = :1 "
" AND refobjid = :2 "
" AND refobjsubid = :3 ",
ObjectIdGetDatum(RelationRelationId),
ObjectIdGetDatum(tableOid),
Int32GetDatum(attnum)));
while (HeapTupleIsValid(tup = caql_getnext(pcqCtx)))
{
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 &&
get_rel_relkind(deprec->objid) == RELKIND_SEQUENCE)
{
sequenceId = deprec->objid;
break;
}
}
caql_endscan(pcqCtx);
if (OidIsValid(sequenceId))
{
HeapTuple classtup;
Form_pg_class classtuple;
char *nspname;
char *result;
cqContext *relcqCtx;
/* Get the sequence's pg_class entry */
relcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(sequenceId)));
classtup = caql_getnext(relcqCtx);
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for relation %u", sequenceId);
classtuple = (Form_pg_class) GETSTRUCT(classtup);
/* Get the namespace */
nspname = get_namespace_name(classtuple->relnamespace);
if (!nspname)
elog(ERROR, "cache lookup failed for namespace %u",
classtuple->relnamespace);
/* And construct the result string */
result = quote_qualified_identifier(nspname,
NameStr(classtuple->relname));
caql_endscan(relcqCtx);
PG_RETURN_TEXT_P(string_to_text(result));
}
PG_RETURN_NULL();
}
/*
* 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)
{
return deparse_expression_pretty(expr, dpcontext, forceprefix,
showimplicit, 0, 0);
}
/* ----------
* deparse_expr_sweet - CDB: expression deparser for EXPLAIN
*
* calls deparse_expression_pretty with minimal parentheses but no indenting.
*/
char *
deparse_expr_sweet(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit)
{
return deparse_expression_pretty(expr, dpcontext, forceprefix,
showimplicit, PRETTYFLAG_PAREN, 0);
}
/* ----------
* 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)
{
StringInfoData buf;
deparse_context context;
initStringInfo(&buf);
context.buf = &buf;
context.namespaces = dpcontext;
context.varprefix = forceprefix;
context.prettyFlags = prettyFlags;
context.indentLevel = startIndent;
context.query = NULL;
get_rule_expr(expr, &context, showimplicit);
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;
RangeTblEntry *rte;
dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
/* Build a minimal RTE for the rel */
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = relid;
rte->eref = makeAlias(aliasname, NIL);
rte->inh = false;
rte->inFromCl = true;
/* Build one-element rtable */
dpns->rtable = list_make1(rte);
dpns->ctes = NIL;
dpns->outer_varno = dpns->inner_varno = 0;
dpns->outer_rte = dpns->inner_rte = NULL;
/* Return a one-deep namespace stack */
return list_make1(dpns);
}
/*
* deparse_context_for_plan - Build deparse context for a plan node
*
* The plan node may contain references to one or two subplans or outer
* join plan nodes. For these, pass the varno used plus a context node
* made with deparse_context_for_subplan. (Pass 0/NULL for unused inputs.)
*
* The plan's rangetable list must also be passed. We actually prefer to use
* the rangetable to resolve simple Vars, but the subplan inputs are needed
* for Vars that reference expressions computed in subplan target lists.
*/
List *
deparse_context_for_plan(int outer_varno, Node *outercontext,
int inner_varno, Node *innercontext,
List *rtable)
{
deparse_namespace *dpns;
dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
dpns->rtable = rtable;
dpns->ctes = NIL;
dpns->outer_varno = outer_varno;
dpns->outer_rte = (RangeTblEntry *) outercontext;
dpns->inner_varno = inner_varno;
dpns->inner_rte = (RangeTblEntry *) innercontext;
/* Return a one-deep namespace stack */
return list_make1(dpns);
}
/*
* deparse_context_for_subplan - Build deparse context for a plan node
*
* Helper routine to build one of the inputs for deparse_context_for_plan.
* Pass the name to be used to reference the subplan, plus the Plan node.
* (subplan really ought to be declared as "Plan *", but we use "Node *"
* to avoid having to include plannodes.h in builtins.h.)
*
* The returned node is actually a RangeTblEntry, but we declare it as just
* Node to discourage callers from assuming anything.
*/
Node *
deparse_context_for_subplan(const char *name, Node *subplan)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
/*
* We create an RTE_SPECIAL RangeTblEntry, and store the subplan in its
* funcexpr field. RTE_SPECIAL nodes shouldn't appear in deparse contexts
* otherwise.
*/
rte->rtekind = RTE_SPECIAL;
rte->relid = InvalidOid;
rte->funcexpr = subplan;
rte->alias = NULL;
rte->eref = name ? makeAlias(name, NIL) : NULL; /*CDB*/
rte->inh = false;
rte->inFromCl = true;
return (Node *) rte;
}
/* ----------
* 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;
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
List *actions = NIL;
int fno;
Datum dat;
bool isnull;
/*
* 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);
/* these could be nulls */
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);
/*
* Build the rules definition text
*/
appendStringInfo(buf, "CREATE RULE %s AS",
quote_identifier(rulename));
if (prettyFlags & PRETTYFLAG_INDENT)
appendStringInfoString(buf, "\n ON ");
else
appendStringInfoString(buf, " ON ");
/* The event the rule is fired for */
switch (ev_type)
{
case '1':
appendStringInfo(buf, "SELECT");
break;
case '2':
appendStringInfo(buf, "UPDATE");
break;
case '3':
appendStringInfo(buf, "INSERT");
break;
case '4':
appendStringInfo(buf, "DELETE");
break;
default:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rule \"%s\" has unsupported event type %d",
rulename, ev_type)));
break;
}
/* The relation the rule is fired on */
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 the rule has an event qualification, add it */
if (ev_qual == NULL)
ev_qual = "";
if (strlen(ev_qual) > 0 && strcmp(ev_qual, "<>") != 0)
{
Node *qual;
Query *query;
deparse_context context;
deparse_namespace dpns;
if (prettyFlags & PRETTYFLAG_INDENT)
appendStringInfoString(buf, "\n ");
appendStringInfo(buf, " WHERE ");
qual = 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);
/* Must acquire locks right away; see notes in get_query_def() */
AcquireRewriteLocks(query);
context.buf = buf;
context.namespaces = list_make1(&dpns);
context.varprefix = (list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
context.indentLevel = PRETTYINDENT_STD;
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
dpns.outer_varno = dpns.inner_varno = 0;
dpns.outer_rte = dpns.inner_rte = NULL;
get_rule_expr(qual, &context, false);
}
appendStringInfo(buf, " DO ");
/* The INSTEAD keyword (if so) */
if (is_instead)
appendStringInfo(buf, "INSTEAD ");
/* Finally the rules actions */
if (list_length(actions) > 1)
{
ListCell *action;
Query *query;
appendStringInfo(buf, "(");
foreach(action, actions)
{
query = (Query *) lfirst(action);
get_query_def(query, buf, NIL, NULL, prettyFlags, 0);
if (prettyFlags)
appendStringInfo(buf, ";\n");
else
appendStringInfo(buf, "; ");
}
appendStringInfo(buf, ");");
}
else if (list_length(actions) == 0)
{
appendStringInfo(buf, "NOTHING;");
}
else
{
Query *query;
query = (Query *) linitial(actions);
get_query_def(query, buf, NIL, NULL, prettyFlags, 0);
appendStringInfo(buf, ";");
}
}
/* ----------
* make_viewdef - reconstruct the SELECT part of a
* view rewrite rule
* ----------
*/
static void
make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags)
{
Query *query;
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
List *actions = NIL;
Relation ev_relation;
int fno;
bool isnull;
/*
* 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);
get_query_def(query, buf, NIL, RelationGetDescr(ev_relation),
prettyFlags, 0);
appendStringInfo(buf, ";");
heap_close(ev_relation, AccessShareLock);
}
/* ----------
* 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.
* ----------
*/
static void
get_query_def(Query *query, StringInfo buf, List *parentnamespace,
TupleDesc resultDesc, int prettyFlags, int startIndent)
{
deparse_context context;
deparse_namespace dpns;
/*
* 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!
*/
AcquireRewriteLocks(query);
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
context.varprefix = (parentnamespace != NIL ||
list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
context.indentLevel = startIndent;
context.query = query;
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
dpns.outer_varno = dpns.inner_varno = 0;
dpns.outer_rte = dpns.inner_rte = NULL;
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_NOTHING:
appendStringInfo(buf, "NOTHING");
break;
case CMD_UTILITY:
get_utility_query_def(query, &context);
break;
default:
elog(ERROR, "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;
appendStringInfoString(buf, "VALUES ");
foreach(vtl, values_lists)
{
List *sublist = (List *) lfirst(vtl);
bool first_col = true;
ListCell *lc;
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;
ListCell *l;
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;
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 (");
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
context->prettyFlags, context->indentLevel);
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;
bool force_colno;
ListCell *l;
/* Insert the WITH clause if given */
get_with_clause(query, context);
/*
* If the Query node has a setOperations tree, then it's the top level of
* a UNION/INTERSECT/EXCEPT query; only the ORDER BY and LIMIT fields are
* interesting in the top query itself.
*/
if (query->setOperations)
{
get_setop_query(query->setOperations, query, context, resultDesc);
/* ORDER BY clauses must be simple in this case */
force_colno = true;
}
else
{
get_basic_select_query(query, context, resultDesc);
force_colno = false;
}
/* Add the ORDER BY clause if given */
if (query->sortClause != NIL)
{
get_sortlist_expr(query->sortClause,
query->targetList,
force_colno,
context,
" ORDER BY ");
}
/* Add the LIMIT clause if given */
if (query->limitOffset != NULL)
{
appendContextKeyword(context, " OFFSET ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
get_rule_expr(query->limitOffset, context, false);
}
if (query->limitCount != NULL)
{
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);
}
/* Add the SCATTER BY clause, if given */
if (query->scatterClause)
{
appendContextKeyword(context, " SCATTER ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
/* Distinguish between RANDOMLY and BY <expr-list> */
if (list_length(query->scatterClause) == 1 &&
linitial(query->scatterClause) == NULL)
{
appendStringInfo(buf, "RANDOMLY");
}
else
{
ListCell *lc;
appendStringInfo(buf, "BY ");
foreach(lc, query->scatterClause)
{
Node *expr = (Node *) lfirst(lc);
get_rule_expr(expr, context, false);
if (lc->next)
appendStringInfo(buf, ", ");
}
}
}
/* Add FOR UPDATE/SHARE clauses if present */
foreach(l, query->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
if (rc->forUpdate)
appendContextKeyword(context, " FOR UPDATE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
else
appendContextKeyword(context, " FOR SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
appendStringInfo(buf, " OF %s",
quote_identifier(rte->eref->aliasname));
if (rc->noWait)
appendStringInfo(buf, " NOWAIT");
}
}
static void
get_basic_select_query(Query *query, deparse_context *context,
TupleDesc resultDesc)
{
StringInfo buf = context->buf;
char *sep;
ListCell *l;
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. If the jointree contains just a single VALUES RTE, we assume
* this case applies (without looking at the targetlist...)
*/
if (list_length(query->jointree->fromlist) == 1)
{
RangeTblRef *rtr = (RangeTblRef *) linitial(query->jointree->fromlist);
if (IsA(rtr, RangeTblRef))
{
RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
if (rte->rtekind == RTE_VALUES)
{
get_values_def(rte->values_lists, context);
return;
}
}
}
/*
* Build up the query string - first we say SELECT
*/
appendStringInfo(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
{
if (has_distinct_on_clause(query))
{
appendStringInfo(buf, " DISTINCT ON (");
sep = "";
foreach(l, query->distinctClause)
{
SortClause *srt = (SortClause *) lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(srt, query->targetList,
false, context);
sep = ", ";
}
appendStringInfo(buf, ")");
}
else
appendStringInfo(buf, " DISTINCT");
}
/* Then we tell what to select (the targetlist) */
get_target_list(query->targetList, context, resultDesc);
/* Add the FROM clause if needed */
get_from_clause(query, " FROM ", context);
/* Add the WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
/* Add the GROUP BY clause if given */
if (query->groupClause != NULL)
{
appendContextKeyword(context, " GROUP BY ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_grouplist(query->groupClause, query->targetList, false, context);
}
/* Add the HAVING clause if given */
if (query->havingQual != NULL)
{
appendContextKeyword(context, " HAVING ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
get_rule_expr(query->havingQual, context, false);
}
/* The WINDOW clause must be last */
if (query->windowClause)
{
bool first = true;
foreach(l, query->windowClause)
{
WindowSpec *spec = (WindowSpec *) lfirst(l);
/* unnamed windows will be displayed in the target list */
if (!spec->name)
continue;
if (first)
{
appendContextKeyword(context, " WINDOW",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
first = false;
}
else
appendStringInfoString(buf, ",");
appendStringInfo(buf, " %s AS ", quote_identifier(spec->name));
get_windowspec_expr(spec, context);
}
}
}
/* ----------
* 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(List *targetList, deparse_context *context,
TupleDesc resultDesc)
{
StringInfo buf = context->buf;
char *sep;
int colno;
ListCell *l;
sep = " ";
colno = 0;
foreach(l, targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
char *colname;
const char *attname;
if (tle->resjunk)
continue; /* ignore junk entries */
appendStringInfoString(buf, sep);
sep = ", ";
colno++;
/*
* 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 want just "foo", instead.
*/
if (tle->expr && IsA(tle->expr, Var))
{
Var *var = (Var *) (tle->expr);
const char *schemaname;
const char *refname;
get_names_for_var(var, 0, context,
&schemaname, &refname, &attname);
if (refname && (context->varprefix || attname == NULL))
{
if (schemaname)
appendStringInfo(buf, "%s.",
quote_identifier(schemaname));
if (strcmp(refname, "*NEW*") == 0)
appendStringInfoString(buf, "new");
else if (strcmp(refname, "*OLD*") == 0)
appendStringInfoString(buf, "old");
else
appendStringInfoString(buf, quote_identifier(refname));
if (attname)
appendStringInfoChar(buf, '.');
}
if (attname)
appendStringInfoString(buf, quote_identifier(attname));
else
{
/*
* In the whole-row Var case, refname is what the default AS
* name would be.
*/
attname = refname;
}
}
else
{
get_rule_expr((Node *) tle->expr, context, true);
/* We'll show the AS name unless it's this: */
attname = "?column?";
}
/*
* 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;
/* Show AS unless the column's name is correct as-is */
if (colname) /* resname could be NULL */
{
if (attname == NULL || strcmp(attname, colname) != 0)
appendStringInfo(buf, " AS %s", quote_identifier(colname));
}
}
}
static void
get_setop_query(Node *setOp, Query *query, deparse_context *context,
TupleDesc resultDesc)
{
StringInfo buf = context->buf;
bool need_paren;
if (IsA(setOp, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) setOp;
RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
Query *subquery = rte->subquery;
Assert(subquery != NULL);
Assert(subquery->setOperations == NULL);
/* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */
need_paren = (subquery->cteList ||
subquery->sortClause ||
subquery->rowMarks ||
subquery->limitOffset ||
subquery->limitCount);
if (need_paren)
appendStringInfoChar(buf, '(');
get_query_def(subquery, buf, context->namespaces, resultDesc,
context->prettyFlags, context->indentLevel);
if (need_paren)
appendStringInfoChar(buf, ')');
}
else if (IsA(setOp, SetOperationStmt))
{
SetOperationStmt *op = (SetOperationStmt *) setOp;
/*
* 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:
elog(ERROR, "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, ')');
}
else
{
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(setOp));
}
}
/*
* Display a list of grouping or (grouping extension) clauses.
*
* The param 'in_grpsets' indicates if the given grplist is
* immediatelly inside a GROUPING SETS clause. This is used
* to determine how to use parantheses.
*/
static void
get_rule_grouplist(List *grplist, List *tlist,
bool in_grpsets, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
ListCell *lc;
sep = "";
foreach (lc, grplist)
{
Node *node = (Node *)lfirst(lc);
Assert (node == NULL ||
IsA(node, List) ||
IsA(node, GroupClause) ||
IsA(node, GroupingClause));
appendStringInfoString(buf, sep);
if (node == NULL)
{
if (!in_grpsets)
appendStringInfoString(buf, "()");
else
continue; /* do nothing */
}
else if (IsA(node, List))
{
appendStringInfoString(buf, "(");
get_rule_grouplist((List *)node, tlist, in_grpsets, context);
appendStringInfoString(buf, ")");
}
else if (IsA(node, GroupClause))
{
if (in_grpsets)
appendStringInfoString(buf, "(");
get_rule_sortgroupclause((GroupClause *)node, tlist,
false,context);
if (in_grpsets)
appendStringInfoString(buf, ")");
}
else
{
get_rule_groupingclause((GroupingClause *)node, tlist,
context);
}
sep = ", ";
}
}
/*
* Display a grouping extension clause.
*/
static void
get_rule_groupingclause(GroupingClause *grp, List *tlist,
deparse_context *context)
{
StringInfo buf = context->buf;
bool in_grpsets = false;
switch(grp->groupType)
{
case GROUPINGTYPE_ROLLUP:
appendStringInfoString(buf, "ROLLUP (");
break;
case GROUPINGTYPE_CUBE:
appendStringInfoString(buf, "CUBE (");
break;
case GROUPINGTYPE_GROUPING_SETS:
in_grpsets = true;
appendStringInfoString(buf, "GROUPING SETS (");
break;
default:
elog(ERROR, "unrecognized grouping type: %d",
grp->groupType);
}
get_rule_grouplist(grp->groupsets, tlist, in_grpsets, context);
appendStringInfoString(buf, ")");
}
/*
* Display a sort/group clause.
*
* Also returns the expression tree, so caller need not find it again.
*/
static Node *
get_rule_sortgroupclause(SortClause *srt, List *tlist, bool force_colno,
deparse_context *context)
{
StringInfo buf = context->buf;
TargetEntry *tle;
Node *expr;
tle = get_sortgroupclause_tle(srt, 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)
{
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;
}
/* ----------
* 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;
char *sep;
ListCell *values_cell;
ListCell *l;
List *strippedexprs;
/*
* If it's an INSERT ... SELECT or VALUES (...), (...), ... there will be
* a single RTE for the SELECT or VALUES.
*/
foreach(l, query->rtable)
{
rte = (RangeTblEntry *) lfirst(l);
if (rte->rtekind == RTE_SUBQUERY)
{
if (select_rte)
elog(ERROR, "too many subquery RTEs in INSERT");
select_rte = rte;
}
if (rte->rtekind == RTE_VALUES)
{
if (values_rte)
elog(ERROR, "too many values RTEs in INSERT");
values_rte = rte;
}
}
if (select_rte && values_rte)
elog(ERROR, "both subquery and values RTEs in INSERT");
/*
* Start the query with INSERT INTO relname
*/
rte = rt_fetch(query->resultRelation, query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context))
{
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoChar(buf, ' ');
}
appendStringInfo(buf, "INSERT INTO %s (",
generate_relation_name(rte->relid, NIL));
/*
* 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)
values_cell = list_head((List *) linitial(values_rte->values_lists));
else
values_cell = NULL;
strippedexprs = NIL;
sep = "";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resjunk)
continue; /* ignore junk entries */
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 (values_cell)
{
/* we discard the stripped expression in this case */
processIndirection((Node *) lfirst(values_cell), context, true);
values_cell = lnext(values_cell);
}
else
{
/* we keep a list of the stripped expressions in this case */
strippedexprs = lappend(strippedexprs,
processIndirection((Node *) tle->expr,
context, true));
}
}
appendStringInfo(buf, ") ");
if (select_rte)
{
/* Add the SELECT */
get_query_def(select_rte->subquery, buf, NIL, NULL,
context->prettyFlags, context->indentLevel);
}
else if (values_rte)
{
/* Add the multi-VALUES expression lists */
get_values_def(values_rte->values_lists, context);
}
else
{
/* Add the single-VALUES expression list */
appendContextKeyword(context, "VALUES (",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
get_rule_expr((Node *) strippedexprs, context, false);
appendStringInfoChar(buf, ')');
}
/* Add RETURNING if present */
if (query->returningList)
{
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context, NULL);
}
}
/* ----------
* get_update_query_def - Parse back an UPDATE parsetree
* ----------
*/
static void
get_update_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
RangeTblEntry *rte;
ListCell *l;
/*
* Start the query with UPDATE relname SET
*/
rte = rt_fetch(query->resultRelation, query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context))
{
appendStringInfoChar(buf, ' ');
context->indentLevel += PRETTYINDENT_STD;
}
appendStringInfo(buf, "UPDATE %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 ");
/* Add the comma separated list of 'attname = value' */
sep = "";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
Node *expr;
if (tle->resjunk)
continue; /* ignore junk entries */
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);
}
/* Add the FROM clause if needed */
get_from_clause(query, " FROM ", context);
/* Add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
/* Add RETURNING if present */
if (query->returningList)
{
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context, NULL);
}
}
/* ----------
* 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;
/*
* Start the query with DELETE FROM relname
*/
rte = rt_fetch(query->resultRelation, query->rtable);
Assert(rte->rtekind == RTE_RELATION);
if (PRETTY_INDENT(context))
{
appendStringInfoChar(buf, ' ');
context->indentLevel += PRETTYINDENT_STD;
}
appendStringInfo(buf, "DELETE FROM %s%s",
only_marker(rte),
generate_relation_name(rte->relid, NIL));
if (rte->alias != NULL)
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->aliasname));
/* Add the USING clause if given */
get_from_clause(query, " USING ", context);
/* Add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
/* Add RETURNING if present */
if (query->returningList)
{
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context, NULL);
}
}
/* ----------
* 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_qualified_identifier(stmt->relation->schemaname,
stmt->relation->relname));
}
else
{
/* Currently only NOTIFY utility commands can appear in rules */
elog(ERROR, "unexpected utility statement type");
}
}
/*
* Get the RTE referenced by a (possibly nonlocal) Var.
*
* The appropriate attribute number is stored into *attno
* (do not assume that var->varattno is what to use).
*
* 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.
*/
static RangeTblEntry *
get_rte_for_var(Var *var, int levelsup, deparse_context *context,
AttrNumber *attno)
{
RangeTblEntry *rte;
int netlevelsup;
deparse_namespace *dpns;
/* default assumption */
*attno = var->varattno;
/* Find appropriate nesting depth */
netlevelsup = var->varlevelsup + levelsup;
if (netlevelsup >= list_length(context->namespaces))
elog(ERROR, "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, INNER, or 0, in which case we try to use
* varnoold instead. If the Var references an expression computed by a
* subplan, varnoold will be 0, and we fall back to looking at the special
* subplan RTEs.
*/
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
rte = rt_fetch(var->varno, dpns->rtable);
else if (var->varnoold >= 1 && var->varnoold <= list_length(dpns->rtable))
{
rte = rt_fetch(var->varnoold, dpns->rtable);
*attno = var->varoattno;
}
else if (var->varno == dpns->outer_varno || var->varno == 0)
rte = dpns->outer_rte;
else if (var->varno == dpns->inner_varno)
rte = dpns->inner_rte;
else
rte = NULL;
return rte;
}
/*
* Get the schemaname, refname and attname for a (possibly nonlocal) Var.
*
* 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.
*
* schemaname is usually returned as NULL. It will be non-null only if
* use of the unqualified refname would find the wrong RTE.
*
* refname will be returned as NULL if the Var references an unnamed join.
* In this case the Var *must* be displayed without any qualification.
*
* attname will be returned as NULL if the Var represents a whole tuple
* of the relation. (Typically we'd want to display the Var as "foo.*",
* but it's convenient to return NULL to make it easier for callers to
* distinguish this case.)
*/
static void
get_names_for_var(Var *var, int levelsup, deparse_context *context,
const char **schemaname,
const char **refname,
const char **attname)
{
RangeTblEntry *rte;
AttrNumber attnum;
/* Find appropriate RTE */
rte = get_rte_for_var(var, levelsup, context, &attnum);
/* Emit results */
*schemaname = NULL; /* default assumptions */
if (rte == NULL ||
rte->rtekind == RTE_VOID)
{
*attname = NULL;
*refname = "*BOGUS*";
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d",
var->varno, var->varattno) ));
return;
}
if (rte->eref)
*refname = rte->eref->aliasname;
else
*refname = NULL;
/* Exceptions occur only if the RTE is alias-less */
if (rte->alias == NULL)
{
if (rte->rtekind == RTE_RELATION)
{
/*
* 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 (rte->eref &&
find_rte_by_refname(rte->eref->aliasname, context) != rte)
*schemaname =
get_namespace_name(get_rel_namespace(rte->relid));
}
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 find 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 return the unqualified
* variable name (this can only happen with columns that were
* merged by USING or NATURAL clauses).
*/
if (attnum > 0)
{
Var *aliasvar;
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
if (IsA(aliasvar, Var))
{
get_names_for_var(aliasvar,
var->varlevelsup + levelsup, context,
schemaname, refname, attname);
return;
}
}
/* Unnamed join has neither schemaname nor refname */
*refname = NULL;
}
else if (rte->rtekind == RTE_SPECIAL)
{
/*
* This case occurs during EXPLAIN when we are looking at a
* deparse context node set up by deparse_context_for_subplan().
* If the subplan tlist provides a name, use it, but usually we'll
* end up with "?columnN?".
*/
TargetEntry *tle = NULL;
if (rte->funcexpr)
tle = get_tle_by_resno(((Plan *)rte->funcexpr)->targetlist, attnum);
if (tle && tle->resname)
{
*attname = tle->resname;
}
else
{
char buf[32];
snprintf(buf, sizeof(buf), "?column%d?", attnum);
*attname = pstrdup(buf);
}
return;
}
}
if (attnum == InvalidAttrNumber)
*attname = NULL;
else
*attname = get_rte_attribute_name(rte, attnum);
}
/*
* Get the name of a field of a Var of type RECORD.
*
* Since no actual table or view column is allowed to have type RECORD, such
* a Var 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.
*
* levelsup is an extra offset to interpret the Var's varlevelsup correctly.
*
* Note: this 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 have a special case for RTE_SPECIAL so that we can
* deal with displaying RECORD-returning functions in subplan targetlists.
*/
static const char *
get_name_for_var_field(Var *var, int fieldno,
int levelsup, deparse_context *context)
{
RangeTblEntry *rte;
AttrNumber attnum;
TupleDesc tupleDesc;
Node *expr;
/* Check my caller didn't mess up */
Assert(IsA(var, Var));
Assert(var->vartype == RECORDOID);
/* Find appropriate RTE */
rte = get_rte_for_var(var, levelsup, context, &attnum);
if (rte == NULL)
{
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d",
var->varno, var->varattno) ));
return "*BOGUS*";
}
if (attnum == InvalidAttrNumber)
{
/* Var is whole-row reference to RTE, so select the right field */
return get_rte_attribute_name(rte, fieldno);
}
expr = (Node *) var; /* default if we can't drill down */
switch (rte->rtekind)
{
case RTE_RELATION:
case RTE_VALUES:
/*
* 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:
{
/* Subselect-in-FROM: examine sub-select's output expr */
TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
attnum);
if (ste == NULL || ste->resjunk)
{
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d",
var->varno, var->varattno) ));
return "*BOGUS*";
}
expr = (Node *) ste->expr;
if (IsA(expr, Var))
{
const char *result = NULL;
/*
* 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;
memset(&mydpns, 0, sizeof(mydpns));
Assert(rte->subquery != NULL);
mydpns.rtable = rte->subquery->rtable;
mydpns.ctes = rte->subquery->cteList;
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 fall through to inspect the expression */
}
break;
case RTE_CTE:
{
/* similar to RTE_SUBQUERY */
CommonTableExpr *cte = NULL;
Index ctelevelsup;
ListCell *lc = NULL;
/*
* Try to find the referenced CTE using the namespace stack.
*/
ctelevelsup = rte->ctelevelsup + levelsup;
if (ctelevelsup < list_length(context->namespaces))
{
deparse_namespace *ctenamespace;
ctenamespace = (deparse_namespace *)
list_nth(context->namespaces, ctelevelsup);
foreach(lc, ctenamespace->ctes)
{
cte = (CommonTableExpr *) lfirst(lc);
if (strcmp(cte->ctename, rte->ctename) == 0)
break;
}
}
if (lc != NULL)
{
Assert(cte != NULL);
TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte), attnum);
if (ste == NULL || ste->resjunk)
{
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d",
var->varno, var->varattno) ));
return "*BOGUS*";
}
expr = (Node *) ste->expr;
if (IsA(expr, Var))
{
const char *result = NULL;
/*
* 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;
deparse_namespace mydpns;
Query *ctequery = (Query *)cte->ctequery;
Assert(ctequery != NULL && IsA(ctequery, Query));
memset(&mydpns, 0, sizeof(mydpns));
mydpns.rtable = ctequery->rtable;
mydpns.ctes = ctequery->cteList;
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 fall through to inspect the expression */
}
}
break;
case RTE_JOIN:
/* Join RTE --- recursively inspect the alias variable */
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);
/* else fall through to inspect the expression */
break;
case RTE_TABLEFUNCTION:
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_SPECIAL:
{
/*
* We are looking at a deparse context node set up by
* deparse_context_for_subplan(). The Var must refer to an
* expression computed by this subplan (or possibly one of its
* inputs), rather than any simple attribute of an RTE entry.
* Look into the subplan's target list to get the referenced
* expression, digging down as far as needed to find something
* that's not a Var, and then pass it to
* get_expr_result_type().
*/
Plan *subplan = (Plan *) rte->funcexpr;
while (subplan)
{
TargetEntry *ste;
ste = get_tle_by_resno(subplan->targetlist,
((Var *) expr)->varattno);
if (!ste || !ste->expr)
break;
expr = (Node *) ste->expr;
if (!IsA(expr, Var))
break;
switch (((Var *)expr)->varno)
{
case 0:
case OUTER:
subplan = outerPlan(subplan);
break;
case INNER:
subplan = innerPlan(subplan);
break;
default:
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d "
"subvarno=%d subattno=%d",
var->varno, var->varattno,
((Var *)expr)->varno,
((Var *)expr)->varattno) ));
return "*BOGUS*";
}
}
if (!subplan)
{
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg_internal("bogus var: varno=%d varattno=%d",
var->varno, var->varattno) ));
return "*BOGUS*";
}
}
break;
case RTE_VOID:
/* No references should exist to a deleted RTE. */
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));
/* Got the tupdesc, so we can extract the field name */
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;
foreach(nslist, context->namespaces)
{
deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
ListCell *rtlist;
foreach(rtlist, dpns->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rtlist);
if (rte->eref &&
strcmp(rte->eref->aliasname, refname) == 0)
{
if (result)
return NULL; /* it's ambiguous */
result = rte;
}
}
if (dpns->outer_rte &&
dpns->outer_rte->eref &&
strcmp(dpns->outer_rte->eref->aliasname, refname) == 0)
{
if (result)
return NULL; /* it's ambiguous */
result = dpns->outer_rte;
}
if (dpns->inner_rte &&
dpns->inner_rte->eref &&
strcmp(dpns->inner_rte->eref->aliasname, refname) == 0)
{
if (result)
return NULL; /* it's ambiguous */
result = dpns->inner_rte;
}
if (result)
break;
}
return result;
}
/*
* 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)
{
/* binary operator */
Node *arg1 = (Node *) linitial(args);
Node *arg2 = (Node *) lsecond(args);
const char *op;
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)
return false;
switch (nodeTag(node))
{
case T_Var:
case T_Const:
case T_Param:
case T_CoerceToDomainValue:
case T_SetToDefault:
case T_CurrentOfExpr:
/* single words: always simple */
return true;
case T_ArrayRef:
case T_ArrayExpr:
case T_RowExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_NullIfExpr:
case T_Aggref:
case T_FuncExpr:
case T_PercentileExpr:
/* function-like: name(..) or name[..] */
return true;
/* CASE keywords act as parentheses */
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:
/* maybe simple, check args */
return isSimpleNode((Node *) ((CoerceToDomain *) node)->arg,
node, prettyFlags);
case T_RelabelType:
return isSimpleNode((Node *) ((RelabelType *) node)->arg,
node, prettyFlags);
case T_ConvertRowtypeExpr:
return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
node, prettyFlags);
case T_OpExpr:
{
/* depends on parent node type; needs further checking */
if (prettyFlags & PRETTYFLAG_PAREN && IsA(parentNode, OpExpr))
{
const char *op;
const char *parentOp;
bool is_lopriop;
bool is_hipriop;
bool is_lopriparent;
bool is_hipriparent;
op = get_simple_binary_op_name((OpExpr *) node);
if (!op)
return false;
/* We know only the basic operators + - and * / % */
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)
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; /* op binds tighter than parent */
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;
}
/* else do the same stuff as for T_SubLink et al. */
/* FALL THROUGH */
}
case T_SubLink:
case T_NullTest:
case T_BooleanTest:
case T_DistinctExpr:
switch (nodeTag(parentNode))
{
case T_FuncExpr:
{
/* special handling for casts */
CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST ||
type == COERCE_IMPLICIT_CAST)
return false;
return true; /* own parentheses */
}
case T_BoolExpr: /* lower precedence */
case T_ArrayRef: /* other separators */
case T_ArrayExpr: /* other separators */
case T_RowExpr: /* other separators */
case T_CoalesceExpr: /* own parentheses */
case T_MinMaxExpr: /* own parentheses */
case T_NullIfExpr: /* other separators */
case T_Aggref: /* own parentheses */
case T_CaseExpr: /* other separators */
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)
return true;
break;
case OR_EXPR:
if (parentType == OR_EXPR)
return true;
break;
}
}
return false;
case T_FuncExpr:
{
/* special handling for casts */
CoercionForm type = ((FuncExpr *) parentNode)->funcformat;
if (type == COERCE_EXPLICIT_CAST ||
type == COERCE_IMPLICIT_CAST)
return false;
return true; /* own parentheses */
}
case T_ArrayRef: /* other separators */
case T_ArrayExpr: /* other separators */
case T_RowExpr: /* other separators */
case T_CoalesceExpr: /* own parentheses */
case T_MinMaxExpr: /* own parentheses */
case T_NullIfExpr: /* other separators */
case T_Aggref: /* own parentheses */
case T_CaseExpr: /* other separators */
return true;
default:
return false;
}
default:
break;
}
/* those we don't know: in dubio complexo */
return false;
}
/*
* appendStringInfoSpaces - append spaces to buffer
*/
static void
appendStringInfoSpaces(StringInfo buf, int count)
{
while (count-- > 0)
appendStringInfoChar(buf, ' ');
}
/*
* 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 need_paren;
need_paren = PRETTY_PAREN(context) &&
!isSimpleNode(node, parentNode, context->prettyFlags);
if (need_paren)
appendStringInfoChar(context->buf, '(');
get_rule_expr(node, context, showimplicit);
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)
{
StringInfo buf = context->buf;
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.
*
* There might be some work left here to support additional node types.
*/
switch (nodeTag(node))
{
case T_Var:
{
Var *var = (Var *) node;
const char *schemaname;
const char *refname;
const char *attname;
get_names_for_var(var, 0, context,
&schemaname, &refname, &attname);
if (refname && (context->varprefix || attname == NULL))
{
if (schemaname)
appendStringInfo(buf, "%s.",
quote_identifier(schemaname));
if (strcmp(refname, "*NEW*") == 0)
appendStringInfoString(buf, "new.");
else if (strcmp(refname, "*OLD*") == 0)
appendStringInfoString(buf, "old.");
else
appendStringInfo(buf, "%s.",
quote_identifier(refname));
}
if (attname)
appendStringInfoString(buf, quote_identifier(attname));
else
appendStringInfoString(buf, "*");
}
break;
case T_Const:
get_const_expr((Const *) node, context, 0);
break;
case T_Param:
appendStringInfo(buf, "$%d", ((Param *) node)->paramid);
break;
case T_Grouping:
appendStringInfo(buf, "Grouping");
break;
case T_GroupId:
appendStringInfo(buf, "group_id()");
break;
case T_GroupingFunc:
get_groupingfunc_expr((GroupingFunc *)node, context);
break;
case T_Aggref:
get_agg_expr((Aggref *) node, context);
break;
case T_WindowRef:
get_windowref_expr((WindowRef *)node, context);
break;
case T_ArrayRef:
{
ArrayRef *aref = (ArrayRef *) node;
bool need_parens;
/*
* 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);
if (need_parens)
appendStringInfoChar(buf, ')');
printSubscripts(aref, context);
/*
* Array assignment nodes should have been handled in
* processIndirection().
*/
if (aref->refassgnexpr)
elog(ERROR, "unexpected refassgnexpr");
}
break;
case T_FuncExpr:
get_func_expr((FuncExpr *) node, context, showimplicit);
break;
case T_OpExpr:
get_oper_expr((OpExpr *) node, context);
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);
appendStringInfo(buf, " IS DISTINCT FROM ");
get_rule_expr_paren(arg2, context, true, node);
if (!PRETTY_PAREN(context))
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);
appendStringInfo(buf, " %s %s (",
generate_operator_name(expr->opno,
exprType(arg1),
get_element_type(exprType(arg2))),
expr->useOr ? "ANY" : "ALL");
get_rule_expr_paren(arg2, context, true, node);
appendStringInfoChar(buf, ')');
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
}
break;
case T_BoolExpr:
{
BoolExpr *expr = (BoolExpr *) node;
Node *first_arg = 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);
while (arg)
{
appendStringInfo(buf, " AND ");
get_rule_expr_paren((Node *) lfirst(arg), context,
false, node);
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);
while (arg)
{
appendStringInfo(buf, " OR ");
get_rule_expr_paren((Node *) lfirst(arg), context,
false, node);
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);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
break;
default:
elog(ERROR, "unrecognized boolop: %d",
(int) expr->boolop);
}
}
break;
case T_SubLink:
get_sublink_expr((SubLink *) node, context);
break;
case T_SubPlan:
{
/*
* We cannot see an already-planned subplan in rule deparsing,
* only while EXPLAINing a query plan. For now, just punt.
*/
if (((SubPlan *) node)->useHashTable)
appendStringInfo(buf, "(hashed subplan)");
else
appendStringInfo(buf, "(subplan)");
}
break;
case T_FieldSelect:
{
FieldSelect *fselect = (FieldSelect *) node;
Node *arg = (Node *) fselect->arg;
int fno = fselect->fieldnum;
const char *fieldname;
bool need_parens;
/*
* 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);
if (need_parens)
appendStringInfoChar(buf, ')');
/*
* If it's a Var of type RECORD, we have to find what the Var
* refers to; otherwise 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(arg, Var) &&
((Var *) arg)->vartype == RECORDOID)
fieldname = get_name_for_var_field((Var *) arg, fno,
0, context);
else
{
TupleDesc tupdesc;
if (get_expr_result_type(arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
tupdesc = lookup_rowtype_tupdesc_copy(exprType(arg),
exprTypmod(arg));
Assert(tupdesc);
/* Got the tupdesc, so we can extract the field name */
Assert(fno >= 1 && fno <= tupdesc->natts);
fieldname = NameStr(tupdesc->attrs[fno - 1]->attname);
}
appendStringInfo(buf, ".%s", quote_identifier(fieldname));
}
break;
case T_FieldStore:
/*
* We shouldn't see FieldStore here; it should have been stripped
* off by processIndirection().
*/
elog(ERROR, "unexpected FieldStore");
break;
case T_RelabelType:
{
RelabelType *relabel = (RelabelType *) node;
Node *arg = (Node *) relabel->arg;
if (relabel->relabelformat == COERCE_IMPLICIT_CAST &&
!showimplicit)
{
/* don't show the implicit cast */
get_rule_expr_paren(arg, context, false, node);
}
else
{
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, false, node);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
appendStringInfo(buf, "::%s",
format_type_with_typemod(relabel->resulttype,
relabel->resulttypmod));
}
}
break;
case T_ConvertRowtypeExpr:
{
ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
Node *arg = (Node *) convert->arg;
if (convert->convertformat == COERCE_IMPLICIT_CAST &&
!showimplicit)
{
/* don't show the implicit cast */
get_rule_expr_paren(arg, context, false, node);
}
else
{
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, false, node);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
appendStringInfo(buf, "::%s",
format_type_with_typemod(convert->resulttype, -1));
}
}
break;
case T_CaseExpr:
{
CaseExpr *caseexpr = (CaseExpr *) node;
ListCell *temp;
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 (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "WHEN ",
0, 0, 0);
if (caseexpr->arg)
{
/*
* The parser should have produced WHEN clauses of the
* form "CaseTestExpr = RHS"; we want to show just the
* RHS. If the user wrote something silly like "CASE
* boolexpr WHEN TRUE THEN ...", then the optimizer's
* simplify_boolean_equality() may have reduced this
* to just "CaseTestExpr" or "NOT CaseTestExpr", for
* which we have to show "TRUE" or "FALSE". Also,
* depending on context the original CaseTestExpr
* might have been reduced to a Const (but we won't
* see "WHEN Const").
*/
if (IsA(w, OpExpr))
{
OpExpr *opexpr = (OpExpr *)w;
Node *rhs;
Assert(IsA(linitial(opexpr->args), CaseTestExpr) ||
IsA(linitial(opexpr->args), Const) ||
IsA(linitial(opexpr->args), RelabelType));
rhs = (Node *) lsecond(opexpr->args);
get_rule_expr(rhs, context, false);
}
else if (IsA(w, CaseTestExpr))
appendStringInfo(buf, "TRUE");
else if (not_clause(w))
{
Expr *arg = get_notclausearg((Expr *) w);
if (IsA(arg, CaseTestExpr))
appendStringInfo(buf, "FALSE");
else
{
/* WHEN IS NOT DISTINCT FROM */
DistinctExpr *dexpr;
Node *rhs;
Insist(IsA(arg, DistinctExpr));
dexpr = (DistinctExpr *) arg;
appendStringInfo(buf, "IS NOT DISTINCT FROM ");
rhs = (Node *) lsecond(dexpr->args);
get_rule_expr(rhs, context, false);
}
}
else
elog(ERROR, "unexpected CASE WHEN clause: %d",
(int) nodeTag(w));
}
else
get_rule_expr(w, context, false);
appendStringInfo(buf, " THEN ");
get_rule_expr((Node *) when->result, context, true);
}
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "ELSE ",
0, 0, 0);
get_rule_expr((Node *) caseexpr->defresult, context, true);
if (!PRETTY_INDENT(context))
appendStringInfoChar(buf, ' ');
appendContextKeyword(context, "END",
-PRETTYINDENT_VAR, 0, 0);
}
break;
case T_ArrayExpr:
{
ArrayExpr *arrayexpr = (ArrayExpr *) node;
appendStringInfo(buf, "ARRAY[");
get_rule_expr((Node *) arrayexpr->elements, context, true);
appendStringInfoChar(buf, ']');
}
break;
case T_TableValueExpr:
{
TableValueExpr *tabexpr = (TableValueExpr *) node;
Query *subquery = (Query*) tabexpr->subquery;
appendStringInfo(buf, "TABLE(");
get_query_def(subquery, buf, context->namespaces, NULL,
context->prettyFlags, context->indentLevel);
appendStringInfoChar(buf, ')');
}
break;
case T_RowExpr:
{
RowExpr *rowexpr = (RowExpr *) node;
TupleDesc tupdesc = NULL;
ListCell *arg;
int i;
char *sep;
/*
* 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);
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;
char *sep;
/*
* 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);
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(linitial(rcexpr->largs)),
exprType(linitial(rcexpr->rargs))));
sep = "";
foreach(arg, rcexpr->rargs)
{
Node *e = (Node *) lfirst(arg);
appendStringInfoString(buf, sep);
get_rule_expr(e, context, true);
sep = ", ";
}
appendStringInfo(buf, "))");
}
break;
case T_CoalesceExpr:
{
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
appendStringInfo(buf, "COALESCE(");
get_rule_expr((Node *) coalesceexpr->args, context, true);
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;
}
get_rule_expr((Node *) minmaxexpr->args, context, true);
appendStringInfoChar(buf, ')');
}
break;
case T_NullIfExpr:
{
NullIfExpr *nullifexpr = (NullIfExpr *) node;
appendStringInfo(buf, "NULLIF(");
get_rule_expr((Node *) nullifexpr->args, context, true);
appendStringInfoChar(buf, ')');
}
break;
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node *) ntest->arg, context, true, node);
switch (ntest->nulltesttype)
{
case IS_NULL:
appendStringInfo(buf, " IS NULL");
break;
case IS_NOT_NULL:
appendStringInfo(buf, " IS NOT NULL");
break;
default:
elog(ERROR, "unrecognized nulltesttype: %d",
(int) ntest->nulltesttype);
}
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);
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:
elog(ERROR, "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)
{
/* don't show the implicit cast */
get_rule_expr(arg, context, false);
}
else
{
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, false, node);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
appendStringInfo(buf, "::%s",
format_type_with_typemod(ctest->resulttype,
ctest->resulttypmod));
}
}
break;
case T_CoerceToDomainValue:
appendStringInfo(buf, "VALUE");
break;
case T_SetToDefault:
appendStringInfo(buf, "DEFAULT");
break;
case T_CurrentOfExpr:
appendStringInfo(buf, "CURRENT OF %s",
quote_identifier(((CurrentOfExpr *) node)->cursor_name));
break;
case T_PercentileExpr:
{
PercentileExpr *p = (PercentileExpr *) node;
if (p->perckind == PERC_MEDIAN)
{
Node *expr;
expr = get_sortgroupclause_expr(linitial(p->sortClause), p->sortTargets);
appendStringInfoString(buf, "median(");
get_rule_expr(expr, context, false);
appendStringInfoString(buf, ")");
}
else
{
if (p->perckind == PERC_CONT)
{
appendStringInfoString(buf, "percentile_cont(");
}
else if (p->perckind == PERC_DISC)
{
appendStringInfoString(buf, "percentile_disc(");
}
else
Assert(false);
get_rule_expr((Node *) p->args, context, true);
appendStringInfoString(buf, ") WITHIN GROUP ");
get_sortlist_expr(p->sortClause,
p->sortTargets,
false, context, "ORDER BY ");
}
}
break;
case T_List:
{
char *sep;
ListCell *l;
sep = "";
foreach(l, (List *) node)
{
appendStringInfoString(buf, sep);
get_rule_expr((Node *) lfirst(l), context, showimplicit);
sep = ", ";
}
}
break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
break;
}
}
/*
* get_oper_expr - Parse back an OpExpr node
*/
static void
get_oper_expr(OpExpr *expr, deparse_context *context)
{
StringInfo buf = context->buf;
Oid opno = expr->opno;
List *args = expr->args;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
if (list_length(args) == 2)
{
/* binary operator */
Node *arg1 = (Node *) linitial(args);
Node *arg2 = (Node *) lsecond(args);
get_rule_expr_paren(arg1, context, true, (Node *) expr);
appendStringInfo(buf, " %s ",
generate_operator_name(opno,
exprType(arg1),
exprType(arg2)));
get_rule_expr_paren(arg2, context, true, (Node *) expr);
}
else
{
/* unary operator --- but which side? */
Node *arg = (Node *) linitial(args);
HeapTuple tp;
Form_pg_operator optup;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_operator "
" WHERE oid = :1 ",
ObjectIdGetDatum(opno)));
tp = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tp))
elog(ERROR, "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);
break;
case 'r':
get_rule_expr_paren(arg, context, true, (Node *) expr);
appendStringInfo(buf, " %s",
generate_operator_name(opno,
exprType(arg),
InvalidOid));
break;
default:
elog(ERROR, "bogus oprkind: %d", optup->oprkind);
}
caql_endscan(pcqCtx);
}
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;
ListCell *l;
/*
* 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 = linitial(expr->args);
Oid rettype = expr->funcresulttype;
int32 coercedTypmod;
/* Get the typmod if this is a length-coercion function */
(void) exprIsLengthCoercion((Node *) expr, &coercedTypmod);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg, context, false, (Node *) expr);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
appendStringInfo(buf, "::%s",
format_type_with_typemod(rettype, coercedTypmod));
return;
}
/*
* Normal function: display as proname(args). First we need to extract
* the argument datatypes.
*/
nargs = 0;
foreach(l, expr->args)
{
if (nargs >= FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("too many arguments")));
argtypes[nargs] = exprType((Node *) lfirst(l));
nargs++;
}
appendStringInfo(buf, "%s(",
generate_function_name(funcoid, nargs, argtypes));
get_rule_expr((Node *) expr->args, context, true);
appendStringInfoChar(buf, ')');
}
/*
* get_groupingfunc_expr - Parse back a grouping function node.
*/
static void
get_groupingfunc_expr(GroupingFunc *grpfunc, deparse_context *context)
{
StringInfo buf = context->buf;
ListCell *lc;
char *sep = "";
List *group_exprs;
if (context->query == NULL)
{
appendStringInfoString(buf, "grouping");
return;
}
group_exprs = get_grouplist_exprs(context->query->groupClause,
context->query->targetList);
appendStringInfoString(buf, "grouping(");
foreach (lc, grpfunc->args)
{
int entry_no = (int)intVal(lfirst(lc));
Node *expr;
Assert (entry_no < list_length(context->query->targetList));
expr = (Node *)list_nth(group_exprs, entry_no);
appendStringInfoString(buf, sep);
get_rule_expr(expr, context, true);
sep = ", ";
}
appendStringInfoString(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;
ListCell *l;
Oid fnoid;
nargs = 0;
foreach(l, aggref->args)
{
if (nargs >= FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("too many arguments")));
argtypes[nargs] = exprType((Node *) lfirst(l));
nargs++;
}
/*
* Depending on the stage of aggregation, this Aggref
* may represent functions that are different from the
* function initially specified. Thus, it is possible that these
* functions take different number of arguments. However,
* this is pretty rare. I think that COUNT(*) is the only one
* so far -- COUNT(*) has no argument in the first stage, while in the
* second stage, we add one argument for COUNT. So COUNT(*) becomes
* COUNT(ANY).
*/
fnoid = aggref->aggfnoid;
switch(aggref->aggstage)
{
case AGGSTAGE_FINAL:
{
if (aggref->aggfnoid == COUNT_STAR_OID)
fnoid = COUNT_ANY_OID;
break;
}
case AGGSTAGE_PARTIAL:
case AGGSTAGE_NORMAL:
default:
break;
}
appendStringInfo(buf, "%s(%s",
generate_function_name(fnoid, nargs, argtypes),
aggref->aggdistinct ? "DISTINCT " : "");
/* aggstar can be set only in zero-argument aggregates */
if (aggref->aggstar)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) aggref->args, context, true);
/* Handle ORDER BY clause for ordered aggregates */
if (aggref->aggorder != NULL && !aggref->aggorder->sortImplicit)
{
get_sortlist_expr(aggref->aggorder->sortClause,
aggref->aggorder->sortTargets,
false, /* force_colno */
context,
" ORDER BY ");
}
appendStringInfoChar(buf, ')');
}
static void
get_windowedge_expr(WindowFrameEdge *edge, deparse_context *context)
{
StringInfo buf = context->buf;
switch(edge->kind)
{
case WINDOW_UNBOUND_PRECEDING:
appendStringInfo(buf, " UNBOUNDED PRECEDING");
break;
case WINDOW_BOUND_PRECEDING:
get_rule_expr(edge->val, context, true);
appendStringInfo(buf, " PRECEDING");
break;
case WINDOW_CURRENT_ROW:
appendStringInfo(buf, " CURRENT ROW");
break;
case WINDOW_BOUND_FOLLOWING:
get_rule_expr(edge->val, context, true);
appendStringInfo(buf, " FOLLOWING");
break;
case WINDOW_UNBOUND_FOLLOWING:
appendStringInfo(buf, " UNBOUNDED FOLLOWING");
break;
default:
elog(ERROR, "unknown frame type");
break;
}
}
static void
get_sortlist_expr(List *l, List *targetList, bool force_colno,
deparse_context *context, char *keyword_clause)
{
ListCell *cell;
char *sep;
StringInfo buf = context->buf;
appendContextKeyword(context, keyword_clause,
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
sep = "";
foreach(cell, l)
{
SortClause *srt = (SortClause *) lfirst(cell);
Node *sortexpr;
Oid sortcoltype;
TypeCacheEntry *typentry;
appendStringInfoString(buf, sep);
sortexpr = get_rule_sortgroupclause(srt, targetList, force_colno,
context);
sortcoltype = exprType(sortexpr);
/* See whether operator is default < or > for datatype */
typentry = lookup_type_cache(sortcoltype,
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
if (srt->sortop == typentry->lt_opr)
/* ASC is default, so emit nothing */ ;
else if (srt->sortop == typentry->gt_opr)
appendStringInfo(buf, " DESC");
else
appendStringInfo(buf, " USING %s",
generate_operator_name(srt->sortop,
sortcoltype,
sortcoltype));
sep = ", ";
}
}
static void
get_windowspec_expr(WindowSpec *spec, deparse_context *context)
{
StringInfo buf = context->buf;
appendStringInfoChar(buf, '(');
if (spec->parent)
{
appendStringInfo(buf, "%s", quote_identifier(spec->parent));
}
else
{
/* parent and partition are mutually exclusive */
if (spec->partition)
get_sortlist_expr(spec->partition,
context->query->targetList,
false, /* force_colno */
context,
"PARTITION BY ");
}
if (spec->order)
{
/*
* If spec has a parent and that parent defines ordering, don't
* display the order here.
*/
bool display_order = true;
if (spec->parent)
{
ListCell *l;
foreach(l, context->query->windowClause)
{
WindowSpec *tmp = (WindowSpec *)lfirst(l);
if (tmp->name && strcmp(spec->parent, tmp->name) == 0 &&
tmp->order)
{
display_order = false;
break;
}
}
}
if (display_order)
{
get_sortlist_expr(spec->order,
context->query->targetList,
false, /* force_colno */
context,
" ORDER BY ");
}
}
if (spec->frame)
{
WindowFrame *f = spec->frame;
/*
* Like the ORDER-BY clause, if spec has a parent and that
* parent defines framing, don't display the frame clause
* here.
*/
bool display_frame = true;
if (spec->parent)
{
ListCell *l;
foreach(l, context->query->windowClause)
{
WindowSpec *tmp = (WindowSpec *)lfirst(l);
if (tmp->name && strcmp(spec->parent, tmp->name) == 0 &&
tmp->frame)
{
display_frame = false;
break;
}
}
}
if (display_frame)
{
appendStringInfo(buf, " %s ", f->is_rows ? "ROWS" : "RANGE");
if (f->is_between)
{
appendStringInfo(buf, "BETWEEN ");
get_windowedge_expr(f->trail, context);
appendStringInfo(buf, " AND ");
get_windowedge_expr(f->lead, context);
}
else
{
get_windowedge_expr(f->trail, context);
}
}
/* exclusion statement */
switch(f->exclude)
{
case WINDOW_EXCLUSION_NULL:
break;
case WINDOW_EXCLUSION_CUR_ROW:
appendStringInfo(buf, " EXCLUDE CURRENT ROW");
break;
case WINDOW_EXCLUSION_GROUP:
appendStringInfo(buf, " EXCLUDE GROUP");
break;
case WINDOW_EXCLUSION_TIES:
appendStringInfo(buf, " EXCLUDE TIES");
break;
case WINDOW_EXCLUSION_NO_OTHERS:
appendStringInfo(buf, " EXCLUDE NO OTHERS");
break;
default:
elog(ERROR, "invalid exclusion type: %i", f->exclude);
}
}
appendStringInfoChar(buf, ')');
}
/*
* get_windowref_expr - Parse back a WindowRef node
*/
static void
get_windowref_expr(WindowRef *wref, deparse_context *context)
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
ListCell *l;
WindowSpec *spec;
nargs = 0;
foreach(l, wref->args)
{
if (nargs >= FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("too many arguments")));
argtypes[nargs] = exprType((Node *) lfirst(l));
nargs++;
}
appendStringInfo(buf, "%s(",
generate_function_name(wref->winfnoid, nargs, argtypes));
get_rule_expr((Node *) wref->args, context, true);
appendStringInfoChar(buf, ')');
/*
* context->query can be NULL when called from explain.
* In such cases, we do not attempt to extract OVER clause
* details: MPP-20672.
*/
if (context->query == NULL)
{
return;
}
/* now for the OVER clause */
appendStringInfo(buf, " OVER");
spec = (WindowSpec *)list_nth(context->query->windowClause, wref->winspec);
/*
* If the spec has a name, it must be in the WINDOW clause, which is
* displayed later. We shouldn't actually encounter such a window
* ref.
*/
if (spec->name)
{
/* XXX: change this to an assertion later */
elog(ERROR, "internal error");
}
else
{
get_windowspec_expr(spec, context);
}
}
/* ----------
* 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.
* ----------
*/
static void
get_const_expr(Const *constval, deparse_context *context, int showtype)
{
StringInfo buf = context->buf;
Oid typoutput;
bool typIsVarlena;
char *extval;
char *valptr;
bool isfloat = false;
bool needlabel;
if (constval->constisnull)
{
/*
* Always label the type of a NULL constant to prevent misdecisions
* about type when reparsing.
*/
appendStringInfo(buf, "NULL");
if (showtype >= 0)
appendStringInfo(buf, "::%s",
format_type_with_typemod(constval->consttype,
-1));
return;
}
getTypeOutputInfo(constval->consttype,
&typoutput, &typIsVarlena);
extval = OidOutputFunctionCall(typoutput, constval->constvalue);
switch (constval->consttype)
{
case INT2OID:
case INT4OID:
case INT8OID:
case OIDOID:
case FLOAT4OID:
case FLOAT8OID:
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; /* it looks like a float */
}
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:
/*
* 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 = extval; *valptr; valptr++)
{
char ch = *valptr;
if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
appendStringInfoChar(buf, ch);
appendStringInfoChar(buf, ch);
}
appendStringInfoChar(buf, '\'');
break;
}
pfree(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:
/* These types can be left unlabeled */
needlabel = false;
break;
case NUMERICOID:
/* Float-looking constants will be typed as numeric */
needlabel = !isfloat;
break;
default:
needlabel = true;
break;
}
if (needlabel || showtype > 0)
appendStringInfo(buf, "::%s",
format_type_with_typemod(constval->consttype, -1));
}
/* ----------
* get_sublink_expr - Parse back a sublink
* ----------
*/
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;
if (sublink->subLinkType == ARRAY_SUBLINK)
appendStringInfo(buf, "ARRAY(");
else
appendStringInfoChar(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))
{
/* single combining operator */
OpExpr *opexpr = (OpExpr *) sublink->testexpr;
get_rule_expr(linitial(opexpr->args), context, true);
opname = generate_operator_name(opexpr->opno,
exprType(linitial(opexpr->args)),
exprType(lsecond(opexpr->args)));
}
else if (IsA(sublink->testexpr, BoolExpr))
{
/* multiple combining operators, = or <> cases */
char *sep;
ListCell *l;
appendStringInfoChar(buf, '(');
sep = "";
foreach(l, ((BoolExpr *) sublink->testexpr)->args)
{
OpExpr *opexpr = (OpExpr *) lfirst(l);
Assert(IsA(opexpr, OpExpr));
appendStringInfoString(buf, sep);
get_rule_expr(linitial(opexpr->args), context, true);
if (!opname)
opname = generate_operator_name(opexpr->opno,
exprType(linitial(opexpr->args)),
exprType(lsecond(opexpr->args)));
sep = ", ";
}
appendStringInfoChar(buf, ')');
}
else if (IsA(sublink->testexpr, RowCompareExpr))
{
/* multiple combining operators, < <= > >= cases */
RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr;
appendStringInfoChar(buf, '(');
get_rule_expr((Node *) rcexpr->largs, context, true);
opname = generate_operator_name(linitial_oid(rcexpr->opnos),
exprType(linitial(rcexpr->largs)),
exprType(linitial(rcexpr->rargs)));
appendStringInfoChar(buf, ')');
}
else
elog(ERROR, "unrecognized testexpr type: %d",
(int) nodeTag(sublink->testexpr));
}
need_paren = true;
switch (sublink->subLinkType)
{
case EXISTS_SUBLINK:
appendStringInfo(buf, "EXISTS ");
break;
case ANY_SUBLINK:
if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
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;
default:
elog(ERROR, "unrecognized sublink type: %d",
(int) sublink->subLinkType);
break;
}
if (need_paren)
appendStringInfoChar(buf, '(');
get_query_def(query, buf, context->namespaces, NULL,
context->prettyFlags, context->indentLevel);
if (need_paren)
appendStringInfo(buf, "))");
else
appendStringInfoChar(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)
{
StringInfo buf = context->buf;
bool first = true;
ListCell *l;
/*
* 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, query->jointree->fromlist)
{
Node *jtnode = (Node *) lfirst(l);
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
if (!rte->inFromCl)
continue;
}
if (first)
{
appendContextKeyword(context, prefix,
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
first = false;
}
else
appendStringInfoString(buf, ", ");
get_from_clause_item(jtnode, query, context);
}
}
static void
get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
bool gavealias = false;
switch (rte->rtekind)
{
case RTE_RELATION:
/* Normal relation RTE */
appendStringInfo(buf, "%s%s",
only_marker(rte),
generate_relation_name(rte->relid, context->namespaces));
break;
case RTE_SUBQUERY:
/* Subquery RTE */
appendStringInfoChar(buf, '(');
get_query_def(rte->subquery, buf, context->namespaces, NULL,
context->prettyFlags, context->indentLevel);
appendStringInfoChar(buf, ')');
break;
case RTE_CTE:
appendStringInfoString(buf, quote_identifier(rte->ctename));
break;
case RTE_TABLEFUNCTION:
/* Table Function RTE */
/* fallthrough */
case RTE_FUNCTION:
/* Function RTE */
get_rule_expr(rte->funcexpr, context, true);
break;
case RTE_VALUES:
/* Values list RTE */
get_values_def(rte->values_lists, context);
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break;
}
if (rte->alias != NULL)
{
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->aliasname));
gavealias = true;
}
else if (rte->rtekind == RTE_RELATION &&
strcmp(rte->eref->aliasname, get_rel_name(rte->relid)) != 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;
}
else if (rte->rtekind == RTE_FUNCTION || rte->rtekind == RTE_TABLEFUNCTION)
{
/*
* 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;
}
if (rte->rtekind == RTE_FUNCTION || rte->rtekind == RTE_TABLEFUNCTION)
{
if (rte->funccoltypes != NIL)
{
/* Function returning RECORD, reconstruct the columndefs */
if (!gavealias)
appendStringInfo(buf, " AS ");
get_from_clause_coldeflist(rte->eref->colnames,
rte->funccoltypes,
rte->funccoltypmods,
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);
}
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
bool need_paren_on_right;
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);
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:
elog(ERROR, "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
appendContextKeyword(context, " CROSS JOIN ",
-PRETTYINDENT_JOIN,
PRETTYINDENT_JOIN, 1);
break;
case JOIN_LEFT:
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;
default:
elog(ERROR, "unrecognized join type: %d",
(int) j->jointype);
}
}
if (need_paren_on_right)
appendStringInfoChar(buf, '(');
get_from_clause_item(j->rarg, query, context);
if (need_paren_on_right)
appendStringInfoChar(buf, ')');
context->indentLevel -= PRETTYINDENT_JOIN_ON;
if (!j->isNatural)
{
if (j->usingClause)
{
ListCell *col;
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, ')');
/* Yes, it's correct to put alias after the right paren ... */
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
elog(ERROR, "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;
AttrNumber attnum;
bool first = true;
if (alias == NULL || alias->colnames == NIL)
return; /* definitely nothing to do */
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, ')');
}
/*
* 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,
deparse_context *context)
{
StringInfo buf = context->buf;
ListCell *l1;
ListCell *l2;
ListCell *l3;
int i = 0;
appendStringInfoChar(buf, '(');
l2 = list_head(types);
l3 = list_head(typmods);
foreach(l1, names)
{
char *attname = strVal(lfirst(l1));
Oid atttypid;
int32 atttypmod;
atttypid = lfirst_oid(l2);
l2 = lnext(l2);
atttypmod = lfirst_int(l3);
l3 = lnext(l3);
if (i > 0)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s %s",
quote_identifier(attname),
format_type_with_typemod(atttypid, atttypmod));
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.)
*/
static void
get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf)
{
HeapTuple ht_opc;
Form_pg_opclass opcrec;
char *opcname;
char *nspname;
bool isvisible;
cqContext *pcqCtx;
/* Domains use their base type's default opclass */
if (OidIsValid(actual_datatype))
actual_datatype = getBaseType(actual_datatype);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_opclass "
" WHERE oid = :1 ",
ObjectIdGetDatum(opclass)));
ht_opc = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(ht_opc))
elog(ERROR, "cache lookup failed for opclass %u", opclass);
opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
/*
* Special case for ARRAY_OPS: pretend it is default for any array type
*/
if (OidIsValid(actual_datatype))
{
if (opcrec->opcintype == ANYARRAYOID &&
OidIsValid(get_element_type(actual_datatype)))
actual_datatype = opcrec->opcintype;
}
/* Must force use of opclass name if not in search path */
isvisible = OpclassIsVisible(opclass);
if (actual_datatype != opcrec->opcintype || !opcrec->opcdefault ||
!isvisible)
{
/* Okay, we need the opclass name. Do we need to qualify it? */
opcname = NameStr(opcrec->opcname);
if (isvisible)
appendStringInfo(buf, " %s", quote_identifier(opcname));
else
{
nspname = get_namespace_name(opcrec->opcnamespace);
appendStringInfo(buf, " %s.%s",
quote_identifier(nspname),
quote_identifier(opcname));
}
}
caql_endscan(pcqCtx);
}
/*
* 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;
/* lookup tuple type */
typrelid = get_typ_typrelid(fstore->resulttype);
if (!OidIsValid(typrelid))
elog(ERROR, "argument type %s of FieldStore is not a tuple type",
format_type_be(fstore->resulttype));
/*
* Print the field name. Note we assume here that there's only
* one field being assigned to. This is okay in stored rules but
* could be wrong in executable target lists. Presently no
* problem since explain.c doesn't print plan targetlists, but
* someday may have to think of something ...
*/
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;
ListCell *uplist_item;
lowlist_item = list_head(aref->reflowerindexpr); /* could be NULL */
foreach(uplist_item, aref->refupperindexpr)
{
appendStringInfoChar(buf, '[');
if (lowlist_item)
{
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_literal_internal - Quote a literal as required.
*
* NOTE: think not to make this function's behavior change with
* standard_conforming_strings. We don't know where the result
* literal will be used, and so we must generate a result that
* will work with either setting. Take a look at what dblink
* uses this for before thinking you know better.
*/
const char *
quote_literal_internal(const char *literal)
{
char *result;
const char *cp1;
char *cp2;
int len;
len = strlen(literal);
/* We make a worst-case result area; wasting a little space is OK */
result = (char *) palloc(len * 2 + 3);
cp1 = literal;
cp2 = result;
for (; len-- > 0; cp1++)
{
if (*cp1 == '\\')
{
*cp2++ = ESCAPE_STRING_SYNTAX;
break;
}
}
len = strlen(literal);
cp1 = literal;
*cp2++ = '\'';
while (len-- > 0)
{
if (SQL_STR_DOUBLE(*cp1, true))
*cp2++ = *cp1;
*cp2++ = *cp1++;
}
*cp2++ = '\'';
result[cp2 - ((char *) result)] = '\0';
return result;
}
/*
* 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;
const char *ptr;
char *result;
char *optr;
/*
* 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 == '_'))
{
/* okay */
}
else
{
safe = false;
if (ch == '"')
nquotes++;
}
}
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.
*/
const ScanKeyword *keyword = ScanKeywordLookup(ident);
if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
safe = false;
}
if (safe)
return ident; /* no change needed */
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 namespace.ident, or just ident if namespace
* is NULL, quoting each component if necessary. The result is palloc'd.
*/
char *
quote_qualified_identifier(const char *qualifier,
const char *ident)
{
StringInfoData buf;
initStringInfo(&buf);
if (qualifier)
appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
appendStringInfoString(&buf, quote_identifier(ident));
return buf.data;
}
/*
* 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)
{
HeapTuple tp;
Form_pg_class reltup;
bool need_qual;
ListCell *nslist;
char *relname;
char *nspname;
char *result;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relid)));
tp = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for relation %u", relid);
reltup = (Form_pg_class) GETSTRUCT(tp);
relname = NameStr(reltup->relname);
/* Check for conflicting CTE name */
need_qual = false;
foreach(nslist, namespaces)
{
deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
ListCell *ctlist;
foreach(ctlist, dpns->ctes)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist);
if (strcmp(cte->ctename, relname) == 0)
{
need_qual = true;
break;
}
}
if (need_qual)
break;
}
/* Otherwise, qualify the name if not visible in search path */
if (!need_qual)
need_qual = !RelationIsVisible(relid);
/* Qualify the name if not visible in search path */
if (need_qual)
nspname = get_namespace_name(reltup->relnamespace);
else
nspname = NULL;
result = quote_qualified_identifier(nspname, relname);
caql_endscan(pcqCtx);
return result;
}
/*
* 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 types.
* (Arg types matter because of ambiguous-function resolution rules.)
*
* The result includes all necessary quoting and schema-prefixing.
*/
static char *
generate_function_name(Oid funcid, int nargs, Oid *argtypes)
{
HeapTuple proctup;
Form_pg_proc procform;
char *proname;
char *nspname;
char *result;
FuncDetailCode p_result;
Oid p_funcid;
Oid p_rettype;
bool p_retset;
bool p_retstrict;
bool p_retordered;
Oid *p_true_typeids;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_proc "
" WHERE oid = :1 ",
ObjectIdGetDatum(funcid)));
proctup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
proname = NameStr(procform->proname);
Assert(nargs == procform->pronargs);
/*
* 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.
*/
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, nargs, argtypes,
&p_funcid, &p_rettype,
&p_retset, &p_retstrict, &p_retordered,
&p_true_typeids);
if ((p_result == FUNCDETAIL_NORMAL || p_result == FUNCDETAIL_AGGREGATE) &&
p_funcid == funcid)
nspname = NULL;
else
nspname = get_namespace_name(procform->pronamespace);
result = quote_qualified_identifier(nspname, proname);
caql_endscan(pcqCtx);
return 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;
char *nspname;
Operator p_result;
cqContext *pcqCtx;
initStringInfo(&buf);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_operator "
" WHERE oid = :1 ",
ObjectIdGetDatum(operid)));
opertup = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(opertup))
elog(ERROR, "cache lookup failed for operator %u", operid);
operform = (Form_pg_operator) GETSTRUCT(opertup);
oprname = NameStr(operform->oprname);
/*
* 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, list_make1(makeString(oprname)), arg1, arg2,
true, -1);
break;
case 'l':
p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2,
true, -1);
break;
case 'r':
p_result = right_oper(NULL, list_make1(makeString(oprname)), arg1,
true, -1);
break;
default:
elog(ERROR, "unrecognized oprkind: %d", operform->oprkind);
p_result = NULL; /* keep compiler quiet */
break;
}
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)
appendStringInfoChar(&buf, ')');
if (p_result != NULL)
ReleaseOperator(p_result);
caql_endscan(pcqCtx);
return buf.data;
}
/*
* 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;
int slen = strlen(str);
int tlen;
tlen = slen + VARHDRSZ;
result = (text *) palloc(tlen);
SET_VARSIZE(result, tlen);
memcpy(VARDATA(result), str, slen);
pfree(str);
return result;
}
static char *
reloptions_to_string(Datum reloptions)
{
char *result;
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 = DirectFunctionCall1(textin, CStringGetDatum(", "));
txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
result = DatumGetCString(DirectFunctionCall1(textout, txt));
return result;
}
/*
* Generate a C string representing a relation's reloptions, or NULL if none.
*/
static char *
flatten_reloptions(Oid relid)
{
char *result = NULL;
HeapTuple tuple;
Datum reloptions;
bool isnull;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
reloptions = caql_getattr(pcqCtx,
Anum_pg_class_reloptions, &isnull);
if (!isnull)
result = reloptions_to_string(reloptions);
caql_endscan(pcqCtx);
return result;
}
/*
* Build a string for use within the partition level WITH () clause.
*
* The reason this is not just flatten_reloptions() is that for the AOCO case,
* we do not want to emit options stored in pg_class.reloptions, other than
* appendonly=true, orientation=column. All the other values are just
* default values. The real values, where they different from the default, are
* stored in pg_attribute_storage.attoptions. This is handled by
* column_encodings_to_string().
*/
static char *
get_partition_reloptions(Oid relid)
{
char *result = NULL;
HeapTuple tuple;
Datum reloptions;
bool isnull;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_class "
" WHERE oid = :1 ",
ObjectIdGetDatum(relid)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
reloptions = caql_getattr(pcqCtx,
Anum_pg_class_reloptions, &isnull);
if (!isnull)
{
result = reloptions_to_string(reloptions);
}
caql_endscan(pcqCtx);
return result;
}
static void
deparse_part_param(deparse_context *c, List *dat)
{
ListCell *lc;
bool first = true;
foreach(lc, dat)
{
if (!first)
appendStringInfo(c->buf, ", ");
else
first = false;
/* MPP-8258: fix for double precision types that use
* FuncExpr's (vs Consts)
*/
if (IsA(lfirst(lc), Const))
get_const_expr(lfirst(lc), c, 0);
else
get_rule_expr(lfirst(lc), c, false);
}
}
static void
partition_rule_range(deparse_context *c, List *start, bool startinc,
List *end, bool endinc, List *every)
{
List *l1;
l1 = start;
if (l1)
{
appendStringInfoString(c->buf, "START (");
deparse_part_param(c, l1);
appendStringInfo(c->buf, ")%s",
startinc == false ? " EXCLUSIVE" : "");
}
l1 = end;
if (l1)
{
appendStringInfoString(c->buf, " END (");
deparse_part_param(c, l1);
appendStringInfo(c->buf, ")%s",
endinc == true ? " INCLUSIVE" : "");
}
l1 = every;
if (l1)
{
appendStringInfoString(c->buf, " EVERY (");
deparse_part_param(c, l1);
appendStringInfoString(c->buf, ")");
}
}
/*
* MPP-7232: need a check if name was not generated by EVERY
*
* The characteristic of a generated EVERY name is that the name of
* the first partition is a string followed by "_1", and subsequent
* names have the same string prefix with an increment in the numeric
* suffix that corresponds to the rank. So if any partitions within
* the EVERY clause are subsequently dropped, added, split, renamed,
* etc, we cannot regenerate a simple EVERY clause, and have to list
* all of the partitions separately.
*/
static char *
check_first_every_name(char *parname)
{
if (parname)
{
char *str = pstrdup(parname);
char *pnum = NULL;
int len = strlen(parname) - 1;
/*
* MPP-7232: need a check if name was not generated by EVERY
*/
while (len >= 0)
{
if (isdigit((int)str[len]))
{
pnum = str + len;
len--;
continue;
}
if (str[len] == '_')
str[len] = '\0';
break;
}
/* should be parname_1 */
if (pnum &&
( 0 == strcmp(pnum, "1")))
return str;
else
{
pfree(str);
return NULL;
}
}
return NULL;
} /* end check_first_every_name */
static bool
check_next_every_name(char *parname1, char *nextname, int parrank)
{
StringInfoData sid1;
bool bstat = FALSE;
initStringInfo(&sid1);
truncateStringInfo(&sid1, 0);
appendStringInfo(&sid1, "%s_%d", parname1, parrank);
bstat = nextname && (0 == strcmp(sid1.data, nextname));
pfree(sid1.data);
return bstat;
} /* end check_next_every_name */
static char *
make_par_name(char *parname, bool isevery)
{
if (isevery)
{
char *str = pstrdup(parname);
int len = strlen(parname) - 1;
/*
* MPP-7232: need a check if name was not generated by EVERY
*/
while (len >= 0)
{
if (isdigit((int)str[len]))
{
len--;
continue;
}
if (str[len] == '_')
str[len] = '\0';
break;
}
return str;
}
else
return parname;
}
static char *
column_encodings_to_string(Relation rel, Datum *opts, char *sep, int indent)
{
StringInfoData str;
AttrNumber i;
bool need_comma = false;
initStringInfo(&str);
for (i = 0; i < RelationGetNumberOfAttributes(rel); i++)
{
char *attname;
if (rel->rd_att->attrs[i]->attisdropped)
continue;
if (!opts[i])
continue;
if (need_comma)
appendStringInfoString(&str, sep);
attname = NameStr(rel->rd_att->attrs[i]->attname);
/* only defined for pretty printing */
if (indent)
{
appendStringInfoChar(&str, '\n');
appendStringInfoSpaces(&str, indent + 4);
}
appendStringInfo(&str, "COLUMN %s ENCODING (%s)",
quote_identifier(attname),
reloptions_to_string(opts[i]));
need_comma = true;
}
return str.data;
}
static char *
make_partition_column_encoding_str(Oid relid, int indent)
{
char *str;
Relation rel = heap_open(relid, AccessShareLock);
Datum *opts = get_rel_attoptions(relid,
RelationGetNumberOfAttributes(rel));
str = column_encodings_to_string(rel, opts, " ", indent);
heap_close(rel, AccessShareLock);
return str;
}
static char *
partition_rule_def_worker(PartitionRule *rule, Node *start,
Node *end, PartitionRule *end_rule,
Node *every,
Partition *part, bool handleevery, int prettyFlags,
bool bLeafTablename, int indent)
{
StringInfoData str;
deparse_context c;
char *reloptions = NULL;
char *tspaceoptions = NULL;
bool needspace = false;
if (OidIsValid(rule->parchildrelid))
{
StringInfoData sid2;
initStringInfo(&sid2);
truncateStringInfo(&sid2, 0);
/*
* If it's in a nondefault tablespace, say so
* (append after the reloptions)
*/
if (!part->paristemplate)
{
Oid tblspc;
tblspc = get_rel_tablespace(rule->parchildrelid);
if (OidIsValid(tblspc))
{
appendStringInfo(&sid2, " TABLESPACE %s",
quote_identifier(
get_tablespace_name(tblspc)));
tspaceoptions = sid2.data;
}
}
reloptions = get_partition_reloptions(rule->parchildrelid);
if (bLeafTablename) /* MPP-6297: dump by tablename */
{
StringInfoData sid1;
initStringInfo(&sid1);
truncateStringInfo(&sid1, 0);
/* always quote to make WITH (tablename=...) work correctly */
/* MPP-12243: but don't use quote_identifier if already quoted! */
appendStringInfo(&sid1, "tablename=\'%s\'",
get_rel_name(rule->parchildrelid));
/* MPP-7191, MPP-7193: fully-qualify storage type if not
* specified (and not a template)
*/
if (!part->paristemplate)
{
if (!reloptions)
{
appendStringInfo(&sid1, ", %s ",
"appendonly=false");
}
else
{
if (!strstr(reloptions, "appendonly="))
appendStringInfo(&sid1, ", %s ",
"appendonly=false");
if ((!strstr(reloptions, "orientation=")) &&
strstr(reloptions, "appendonly=true"))
appendStringInfo(&sid1, ", %s ",
"orientation=row");
}
}
if (reloptions)
{
appendStringInfo(&sid1, ", %s ", reloptions);
pfree(reloptions);
}
reloptions = sid1.data;
}
}
else if ((PointerIsValid(rule->parreloptions) ||
OidIsValid(rule->partemplatespaceId))
|| (bLeafTablename && part->paristemplate))
{
ListCell *lc;
List *opts;
StringInfoData buf;
StringInfoData sid3;
initStringInfo(&buf);
initStringInfo(&sid3);
truncateStringInfo(&sid3, 0);
/* NOTE: only the template case */
Assert(part->paristemplate);
if (bLeafTablename && part->paristemplate)
{
/* hackery! */
/* MPP-6297: Make a fake tablename for template entries to
* invoke special dump/restore magic for EVERY in
* analyze.c:partition_range_every(). Note that the
* tablename is ignored during SET SUBPARTITION TEMPLATE
* because the template rules do not have corresponding
* relations
*
* MPP-10480: use tablename
*/
appendStringInfo(&buf, "tablename=\'%s\'",
quote_identifier(
get_rel_name(part->parrelid)));
}
opts = rule->parreloptions;
if (PointerIsValid(rule->parreloptions))
{
foreach(lc, opts)
{
DefElem *e = lfirst(lc);
if (strlen(buf.data))
appendStringInfo(&buf, ", ");
appendStringInfoString(&buf, e->defname);
if (e->arg)
appendStringInfo(&buf, "=%s", strVal(e->arg));
}
}
if (strlen(buf.data))
{
reloptions = buf.data;
}
if (OidIsValid(rule->partemplatespaceId))
{
char *tname = get_tablespace_name(rule->partemplatespaceId);
Assert(tname);
appendStringInfo(&sid3, " TABLESPACE %s",
quote_identifier(tname));
tspaceoptions = sid3.data;
}
}
initStringInfo(&str);
c.buf = &str;
c.prettyFlags = prettyFlags;
c.indentLevel = PRETTYINDENT_STD;
if (rule->parisdefault)
{
appendStringInfo(&str, "DEFAULT %sPARTITION %s ",
part->parlevel > 0 ? "SUB" : "",
quote_identifier(rule->parname));
if (reloptions && strlen(reloptions))
appendStringInfo(&str, " WITH (%s)", reloptions);
if (tspaceoptions && strlen(tspaceoptions))
appendStringInfo(&str, " %s", tspaceoptions);
return str.data;
}
if (rule->parname && rule->parname[0] != '\0')
appendStringInfo(&str, "%sPARTITION %s ",
part->parlevel > 0 ? "SUB" : "",
quote_identifier(make_par_name(rule->parname,
handleevery)));
switch (part->parkind)
{
case 'h':
break;
case 'r':
{
/* MPP-7232: Note: distinguish "(start) rule" and
* "end_rule", because for an EVERY clause
* inclusivity/exclusivity can differ
*/
partition_rule_range(&c, (List *)start,
rule->parrangestartincl,
(List *)end,
end_rule->parrangeendincl,
(List *)every);
needspace = true;
}
break;
case 'l':
{
ListCell *lc;
List *l1;
int2 nkeys = part->parnatts;
int2 parcol = 0;
appendStringInfo(&str, "VALUES(");
l1 = (List *)rule->parlistvalues;
/* MPP-5878: print multiple columns if > 1 key cols */
foreach(lc, l1)
{
List *vals = lfirst(lc);
ListCell *lcv = list_head(vals);
if (lc != list_head(l1))
appendStringInfoString(&str, ", ");
if (nkeys > 1) /* extra parens if group multiple cols */
appendStringInfoString(&str, " (");
for (parcol = 0; parcol < nkeys; parcol++)
{
Const *con = lfirst(lcv);
if (lcv != list_head(vals))
appendStringInfoString(&str, ", ");
get_const_expr(con, &c, -1);
lcv = lnext(lcv);
}
if (nkeys > 1) /* extra parens if group multiple cols */
appendStringInfoString(&str, ")");
}
appendStringInfoString(&str, ")");
needspace = true;
}
break;
}
if (reloptions)
appendStringInfo(&str, "%sWITH (%s)",
needspace ? " " : "",
reloptions);
if (tspaceoptions && strlen(tspaceoptions))
{
/* if have reloptions, then need a space, else just use needspace */
bool needspace2 = reloptions ? true : (needspace);
appendStringInfo(&str, "%s%s",
needspace2 ? " " : "",
tspaceoptions);
}
return str.data;
}
/*
* Writes out rule of partition, as well as column compression if any.
*/
static void
write_out_rule(PartitionRule *rule, PartitionNode *pn, Node *start,
Node *end,
PartitionRule *end_rule,
Node *every, deparse_context *head, deparse_context *body,
bool handleevery, bool *needcomma,
bool *first_rule, int16 *leveldone,
PartitionNode *children, bool bLeafTablename)
{
char *str;
if (!*first_rule)
{
appendStringInfoString(body->buf, ", ");
*needcomma = false;
}
if (PRETTY_INDENT(body))
{
appendStringInfoChar(body->buf, '\n');
appendStringInfoSpaces(body->buf,
Max(body->indentLevel, 0) + 2);
}
/* MPP-7232: Note: distinguish "(start) rule" and "end_rule",
* because for an EVERY clause inclusivity/exclusivity
* can differ
*/
str = partition_rule_def_worker(rule, start,
end, end_rule,
every, pn->part, handleevery,
body->prettyFlags,
bLeafTablename,
body->indentLevel);
if (str && strlen(str))
{
if (strlen(body->buf->data) && !first_rule &&
!PRETTY_INDENT(body))
appendStringInfoString(body->buf, " ");
appendStringInfoString(body->buf, str);
*needcomma = true;
}
if (str)
pfree(str);
/*
* We dump per partition column encoding for non-templates,
* and do not dump them for templates.
*/
if (OidIsValid(rule->parchildrelid))
{
int indent_enc = body->indentLevel;
char *col_enc;
/* COLUMN ... ENCODING ( ) for the partition */
if (PRETTY_INDENT(body))
indent_enc += PRETTYINDENT_STD;
col_enc = make_partition_column_encoding_str(rule->parchildrelid,
indent_enc);
if (col_enc && strlen(col_enc) > 0)
{
appendStringInfo(body->buf, " %s", col_enc);
*needcomma = true;
}
if (col_enc)
pfree(col_enc);
}
get_partition_recursive(children, head, body, leveldone, bLeafTablename);
if (*first_rule)
*first_rule = false;
}
static void
get_partition_recursive(PartitionNode *pn, deparse_context *head,
deparse_context *body,
int16 *leveldone, int bLeafTablename)
{
PartitionRule *rule = NULL;
ListCell *lc;
int i;
bool needcomma = false;
bool first_rule = true;
PartitionRule *first_every_rule = NULL;
PartitionRule *prev_rule = NULL;
char *parname1 = NULL;
int parrank = 0;
if (!pn)
return;
if (*leveldone < pn->part->parlevel)
{
if (pn->part->parlevel == 0)
appendStringInfoString(head->buf, "PARTITION BY ");
else if (pn->part->parlevel > 0)
appendContextKeyword(head, "SUBPARTITION BY ",
PRETTYINDENT_STD, 0, 2);
switch (pn->part->parkind)
{
case 'h': appendStringInfoString(head->buf, "HASH"); break;
case 'l': appendStringInfoString(head->buf, "LIST"); break;
case 'r': appendStringInfoString(head->buf, "RANGE"); break;
default:
elog(ERROR, "unknown partitioning kind '%c'",
pn->part->parkind);
break;
}
appendStringInfoChar(head->buf, '(');
for (i = 0; i < pn->part->parnatts; i++)
{
char *attname = get_relid_attribute_name(pn->part->parrelid,
pn->part->paratts[i]);
if (i)
appendStringInfo(head->buf, ", ");
appendStringInfoString(head->buf, quote_identifier(attname));
pfree(attname);
}
appendStringInfoChar(head->buf, ')');
if (pn->part->parkind == 'h')
appendStringInfo(head->buf, " %sPARTITIONS %i ",
pn->part->parlevel > 0 ? "SUB" : "",
list_length(pn->rules));
(*leveldone)++;
}
if (pn->part->parlevel > 0)
appendStringInfoChar(body->buf, ' ');
if (pn->rules || pn->default_part)
appendContextKeyword(body, "(", PRETTYINDENT_STD, 0, 2);
/* iterate through partitions */
foreach(lc, pn->rules)
{
rule = lfirst(lc);
/*
* If we're doing hash partitioning and the first rule doesn't have
* a parname, none will so break out.
*
* XXX: when we support hash, do need to dump these in case they have
* children.
*/
if (pn->part->parkind == 'h' && !strlen(rule->parname))
break;
/*
* RANGE partitions are the interesting case. If the partitions use
* EVERY(), we want to dump a single rule which generates all the rules
* we've expanded from EVERY(), rather than a bunch of rules.
*/
if (pn->part->parkind == 'r')
{
if (!first_every_rule)
{
if (!bLeafTablename && rule->parrangeevery)
{
if (!strlen(rule->parname))
{
first_every_rule = rule;
prev_rule = NULL;
}
else
{
/* MPP-7232: check if name was not generated
* by EVERY
*/
parname1 = check_first_every_name(rule->parname);
if (parname1)
{
parrank = 2;
first_every_rule = rule;
prev_rule = NULL;
}
else
parrank = 0;
}
if (first_every_rule)
continue;
}
}
else if (first_every_rule->parrangeevery)
{
bool estat = equal(first_every_rule->parrangeevery,
rule->parrangeevery);
if (estat)
{
/* check if have a named partition in a block of
* anonymous every partitions
*/
if (rule->parname && strlen(rule->parname) && !parname1)
estat = false;
/* note that the case of an unnamed partition in a
* block of named every partitions is handled by
* check_next_every_name...
*/
}
if (estat && parname1)
{
estat = check_next_every_name(parname1,
rule->parname, parrank);
if (estat)
parrank++;
else
{
parrank = 0;
pfree(parname1);
parname1 = NULL;
}
}
/* ensure that start and end have opposite
* inclusivity, ie start is always inclusive and end
* is always exclusive, with exceptions for the first
* every rule start (which can be exclusive) and the
* last every rule end (which can be inclusive).
*/
if (estat)
estat = (rule->parrangestartincl == true);
if (estat && prev_rule)
estat = (prev_rule->parrangeendincl == false);
/* finally, make sure that the start value matches the
* previous end, ie look for "holes" where a partition
* might have been dropped in the middle of an EVERY
* range...
*/
if (estat && prev_rule)
estat = equal(rule->parrangestart,
prev_rule->parrangeend);
if (estat)
{
prev_rule = rule;
continue;
}
else
{
/* MPP-6297: write out the "every" rule (based
* on the first one), then clear it if we are
* done
*/
write_out_rule(first_every_rule, pn,
first_every_rule->parrangestart,
prev_rule ?
prev_rule->parrangeend :
first_every_rule->parrangeend,
prev_rule ? prev_rule : first_every_rule,
first_every_rule->parrangeevery,
head, body, true, &needcomma, &first_rule,
leveldone,
first_every_rule->children,
bLeafTablename);
if (rule->parrangeevery)
{
first_every_rule = NULL;
if (!strlen(rule->parname))
prev_rule = first_every_rule = rule;
else
{
/* MPP-7232: check if name was not generated
* by EVERY
*/
if (parname1)
{
pfree(parname1);
parname1 = NULL;
}
parname1 =
check_first_every_name(rule->parname);
if (parname1)
{
parrank = 2;
prev_rule = first_every_rule = rule;
}
else
parrank = 0;
}
if (first_every_rule)
continue;
}
else
{
first_every_rule = NULL;
}
}
}
} /* end if range */
/*
* Note that this handles the LIST and HASH cases too */
write_out_rule(rule, pn, rule->parrangestart,
rule->parrangeend,
rule,
rule->parrangeevery, head, body, false, &needcomma,
&first_rule, leveldone,
rule->children, bLeafTablename);
} /* end foreach */
if (first_every_rule)
{
write_out_rule(first_every_rule, pn, first_every_rule->parrangestart,
prev_rule ?
prev_rule->parrangeend :
first_every_rule->parrangeend,
prev_rule ? prev_rule : first_every_rule,
first_every_rule->parrangeevery,
head, body, true, &needcomma, &first_rule, leveldone,
first_every_rule->children,
bLeafTablename);
}
if (pn->default_part)
{
write_out_rule(pn->default_part, pn, NULL, NULL,
NULL, NULL,
head, body, false,
&needcomma,
&first_rule, leveldone, pn->default_part->children,
bLeafTablename);
}
if (pn->rules || pn->default_part)
{
if (pn->part->paristemplate)
{
/* Add column encoding rules at the end */
int indent = 0;
Relation rel = heap_open(pn->part->parrelid, AccessShareLock);
Datum *opts = get_partition_encoding_attoptions(rel,
pn->part->partid);
char *str;
if (PRETTY_INDENT(body))
{
/* subtract 2 for the built in stepping in indentLevel */
indent = body->indentLevel - 2;
if (indent < 0)
indent = 0;
}
str = column_encodings_to_string(rel, opts, ", ", indent);
if (str && strlen(str) > 0)
appendStringInfo(body->buf, ", %s", str);
heap_close(rel, AccessShareLock);
}
appendContextKeyword(body, ")", 0, -PRETTYINDENT_STD, 2);
}
}
/* MPP-6095: dump template definitions */
static char *
pg_get_partition_template_def_worker(Oid relid, int prettyFlags,
int bLeafTablename)
{
Relation rel = heap_open(relid, AccessShareLock);
/* pn is the partition def for the relation, and pnt is the
* associated template defs. We need to walk pn to obtain the
* partition id str's for the ALTER statement.
*/
PartitionNode *pn = RelationBuildPartitionDesc(rel, false);
PartitionNode *pnt = NULL;
StringInfoData head;
StringInfoData body;
StringInfoData altr, sid1, sid2, partidsid;
int16 leveldone = -1;
deparse_context headc;
deparse_context bodyc;
deparse_context partidc;
int templatelevel = 1;
bool bFirstOne = true;
if (!pn)
{
heap_close(rel, AccessShareLock);
return NULL;
}
/* head string for get_partition_recursive() -- just discard this */
initStringInfo(&head);
headc.buf = &head;
headc.prettyFlags = prettyFlags;
headc.indentLevel = 0;
/* body: partition definition associated with template */
initStringInfo(&body);
bodyc.buf = &body;
bodyc.prettyFlags = prettyFlags;
bodyc.indentLevel = 0;
/* altr: the real "head" string (first part of ALTER TABLE statement) */
initStringInfo(&altr);
initStringInfo(&sid1); /* final output string */
initStringInfo(&sid2); /* string for temp storage */
initStringInfo(&partidsid);
partidc.buf = &partidsid;
partidc.prettyFlags = prettyFlags;
partidc.indentLevel = 0;
/* build the initial ALTER TABLE prefix. Append the next level of
* partition depth as iterate thru loop
*/
appendStringInfo(&altr, "ALTER TABLE %s ",
generate_relation_name(relid, NIL));
/* build the text of the SET SUBPARTITION TEMPLATE statements from
* shallowest (level 1) to deepest by walking pn tree rules, but
* resequence statements from deepest to shallowest when we append
* them into the final output string, as we cannot reset the
* shallow template unless the deeper template exists.
*/
while (pn)
{
PartitionRule *prule = NULL;
const char *partIdStr = "";
truncateStringInfo(&head, 0);
truncateStringInfo(&body, 0);
truncateStringInfo(&sid2, 0);
pnt = get_parts(relid,
templatelevel, 0, true, CurrentMemoryContext, true /*includesubparts*/);
get_partition_recursive(pnt, &headc, &bodyc, &leveldone,
bLeafTablename);
/* look at the prule for the default partition (or non-default
* if necessary). We need to build the partition identifier
* for the next level of the tree (used for the next iteration
* of this loop, not the current iteration).
*/
prule = pn->default_part;
if (!prule)
{
if (list_length(pn->rules))
prule = (PartitionRule *)linitial(pn->rules);
}
if (!prule)
break;
if (prule->parname
&& strlen(prule->parname))
{
partIdStr = quote_identifier(prule->parname);
}
else
{
switch (pn->part->parkind)
{
case 'r': /* range */
partIdStr = "FOR (RANK(1))";
break;
case 'l': /* list */
{
ListCell *lc;
List *l1;
int2 nkeys = pn->part->parnatts;
int2 parcol = 0;
truncateStringInfo(&partidsid, 0);
appendStringInfo(&partidsid, "FOR (");
l1 = (List *)prule->parlistvalues;
/* MPP-5878: print multiple columns if > 1 key cols */
foreach(lc, l1)
{
List *vals = lfirst(lc);
ListCell *lcv = list_head(vals);
for (parcol = 0; parcol < nkeys; parcol++)
{
Const *con = lfirst(lcv);
if (lcv != list_head(vals))
appendStringInfoString(&partidsid, ", ");
get_const_expr(con, &partidc, -1);
lcv = lnext(lcv);
}
break;
}
appendStringInfoString(&partidsid, ")");
partIdStr = partidsid.data;
}
break;
default: /* including hash for now... */
elog(ERROR, "unrecognized partitioning kind '%c'",
pn->part->parkind);
break;
}
}
pn = prule->children;
if (pnt)
{
/* move the prior statements to sid2 */
appendStringInfo(&sid2, "%s", sid1.data);
truncateStringInfo(&sid1, 0);
/* build the new statement in sid1, then append the
* previous (shallower) statements
*/
appendStringInfo(&sid1, "%s\nSET SUBPARTITION TEMPLATE %s%s",
altr.data, body.data,
bFirstOne ? "" : ";\n" );
appendStringInfo(&sid1, "\n%s", sid2.data);
/* no trailing semicolon on end of statement -- dumper
* will add it
*/
if (bFirstOne)
bFirstOne = false;
}
/* increase the partitioning depth */
appendStringInfo(&altr, "ALTER PARTITION %s ", partIdStr);
templatelevel++;
} /* end while pn */
heap_close(rel, NoLock);
return sid1.data;
} /* end pg_get_partition_template_def_worker */
static char *
pg_get_partition_def_worker(Oid relid, int prettyFlags, int bLeafTablename)
{
Relation rel = heap_open(relid, AccessShareLock);
PartitionNode *pn = RelationBuildPartitionDesc(rel, false);
StringInfoData head;
StringInfoData body;
int16 leveldone = -1;
deparse_context headc;
deparse_context bodyc;
if (!pn)
{
heap_close(rel, AccessShareLock);
return NULL;
}
initStringInfo(&head);
headc.buf = &head;
headc.prettyFlags = prettyFlags;
headc.indentLevel = 0;
initStringInfo(&body);
bodyc.buf = &body;
bodyc.prettyFlags = prettyFlags;
bodyc.indentLevel = 0;
get_partition_recursive(pn, &headc, &bodyc, &leveldone, bLeafTablename);
heap_close(rel, NoLock);
if (strlen(body.data))
appendStringInfo(&head, " %s", body.data);
pfree(body.data);
return head.data;
}
static char *
get_rule_def_common(Oid partid, int prettyFlags, int bLeafTablename)
{
Relation rel;
cqContext cqc;
HeapTuple tuple;
PartitionRule *rule;
Partition *part;
rel = heap_open(PartitionRuleRelationId, AccessShareLock);
tuple = caql_getfirst(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_partition_rule "
" WHERE oid = :1 ",
ObjectIdGetDatum(partid)));
if (!HeapTupleIsValid(tuple))
{
heap_close(rel, AccessShareLock);
return NULL;
}
rule = ruleMakePartitionRule(tuple, RelationGetDescr(rel),
CurrentMemoryContext);
heap_close(rel, AccessShareLock);
/* lookup pg_partition by oid */
rel = heap_open(PartitionRelationId, AccessShareLock);
tuple = caql_getfirst(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_partition "
" WHERE oid = :1 ",
ObjectIdGetDatum(rule->paroid)));
if (!HeapTupleIsValid(tuple))
{
heap_close(rel, AccessShareLock);
return NULL;
}
part = partMakePartition(tuple, RelationGetDescr(rel),
CurrentMemoryContext);
heap_close(rel, AccessShareLock);
return partition_rule_def_worker(rule, rule->parrangestart,
rule->parrangeend, rule,
rule->parrangeevery, part,
false, prettyFlags, bLeafTablename, 0);
}
Datum
pg_get_partition_rule_def(PG_FUNCTION_ARGS)
{
Oid ruleid = PG_GETARG_OID(0);
char *str;
/* MPP-6297: don't dump by tablename here */
str = get_rule_def_common(ruleid, 0, FALSE);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}
Datum
pg_get_partition_rule_def_ext(PG_FUNCTION_ARGS)
{
Oid partid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
char *str;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
/* MPP-6297: don't dump by tablename here */
str = get_rule_def_common(partid, prettyFlags, FALSE);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}
Datum
pg_get_partition_def(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
char *str;
/* MPP-6297: don't dump by tablename here */
str = pg_get_partition_def_worker(relid, 0, FALSE);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}
Datum
pg_get_partition_def_ext(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
char *str;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
/* MPP-6297: don't dump by tablename here
* NOTE: may need to backport fix to 3.3.x, and changing
* bLeafTablename = TRUE here should only affect
* pg_dump/cdb_dump_agent (and partition.sql test)
*/
str = pg_get_partition_def_worker(relid, prettyFlags, FALSE);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}
/* MPP-6297: final boolean argument to determine whether to dump by
* tablename (normally, only for pg_dump.c/cdb_dump_agent.c)
*/
Datum
pg_get_partition_def_ext2(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
bool bLeafTablename = PG_GETARG_BOOL(2);
int prettyFlags;
char *str;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
/* MPP-6297: dump by tablename */
str = pg_get_partition_def_worker(relid, prettyFlags, bLeafTablename);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}
/* MPP-6095: dump template definitions */
Datum
pg_get_partition_template_def(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
bool bLeafTablename = PG_GETARG_BOOL(2);
char *str;
int prettyFlags = 0;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
str = pg_get_partition_template_def_worker(relid,
prettyFlags, bLeafTablename);
if (!str)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(string_to_text(str));
}