| /********************************************************************** |
| * 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)); |
| } |