blob: 9a580692ba3f3064a149c3bf7aa8d90cb983d038 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* analyze.c
* transform the raw parse tree into a query tree
*
* For optimizable statements, we are careful to obtain a suitable lock on
* each referenced table, and other modules of the backend preserve or
* re-obtain these locks before depending on the results. It is therefore
* okay to do significant semantic analysis of these statements. For
* utility commands, no locks are obtained here (and if they were, we could
* not be sure we'd still have them at execution). Hence the general rule
* for utility commands is to just dump them into a Query node untransformed.
* DECLARE CURSOR and EXPLAIN are exceptions because they contain
* optimizable statements.
*
*
* Portions Copyright (c) 2005-2010, Greenplum inc
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.353.2.1 2007/06/20
*18:21:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "port.h"
#include <uuid/uuid.h>
#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/plugstorage.h"
#include "catalog/catquery.h"
#include "catalog/gp_policy.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_compression.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_encoding.h"
#include "cdb/cdbpartition.h"
#include "cdb/cdbparquetstoragewrite.h"
#include "cdb/cdbdatalocality.h"
#include "catalog/skylon_vlabel.h"
#include "catalog/skylon_vlabel_attribute.h"
#include "catalog/skylon_elabel.h"
#include "catalog/skylon_elabel_attribute.h"
#include "catalog/skylon_graph.h"
#include "catalog/skylon_graph_vlabel.h"
#include "catalog/skylon_graph_elabel.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "nodes/value.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "optimizer/planmain.h"
#include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parse_agg.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parse_cte.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/palloc.h"
#include "utils/syscache.h"
#include "utils/uri.h"
#include "cdb/cdbappendonlyam.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbcat.h"
#include "cdb/cdbhash.h"
#include "cdb/cdbsreh.h"
/* temporary rule to control whether we generate RULEs or not -- for testing */
bool enable_partition_rules = false;
/* State shared by transformCreateSchemaStmt and its subroutines */
typedef struct {
const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */
char *schemaname; /* name of schema */
char *authid; /* owner of schema */
List *sequences; /* CREATE SEQUENCE items */
List *tables; /* CREATE TABLE items */
List *views; /* CREATE VIEW items */
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
List *grants; /* GRANT items */
List *fwconstraints; /* Forward referencing FOREIGN KEY constraints */
List *alters; /* Generated ALTER items (from the above) */
List *ixconstraints; /* index-creating constraints */
List *blist; /* "before list" of things to do before
* creating the schema */
List *alist; /* "after list" of things to do after creating
* the schema */
} CreateSchemaStmtContext;
typedef struct {
Oid *paramTypes;
int numParams;
} check_parameter_resolution_context;
/* Context for transformGroupedWindows() which mutates components
* of a query that mixes windowing and aggregation or grouping. It
* accumulates context for eventual construction of a subquery (the
* grouping query) during mutation of components of the outer query
* (the windowing query).
*/
typedef struct {
List *subtlist; /* target list for subquery */
List *subgroupClause; /* group clause for subquery */
List *windowClause; /* window clause for outer query*/
/* Scratch area for init_grouped_window context and map_sgr_mutator.
*/
Index *sgr_map;
/* Scratch area for grouped_window_mutator and var_for_gw_expr.
*/
List *subrtable;
int call_depth;
TargetEntry *tle;
} grouped_window_ctx;
typedef struct {
ParseState *pstate;
List *cols;
} part_col_cxt;
/* state structures for validing parsed partition specifications */
typedef struct {
ParseState *pstate;
CreateStmtContext *cxt;
CreateStmtBase *stmt;
PartitionBy *pBy;
PartitionElem *pElem;
PartitionElem *prevElem;
Node *spec;
int partNumber;
char *namBuf;
char *at_depth;
bool prevHadName;
int prevStartEnd;
List *allRangeVals;
List *allListVals;
} partValidationState;
typedef struct {
ParseState *pstate;
int location;
} range_partition_ctx;
static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static void parse_analyze_error_callback(void *parsestate); /*CDB*/
static Query *transformStmt(ParseState *pstate, Node *stmt,
List **extras_before, List **extras_after);
static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
/*
* MPP-2506 [insert/update/delete] RETURNING clause not supported:
* We have problems processing the returning clause, so for now we have
* simply removed it and replaced it with an error message.
*/
#define MPP_RETURNING_NOT_SUPPORTED
#ifndef MPP_RETURNING_NOT_SUPPORTED
static List *transformReturningList(ParseState *pstate, List *returningList);
#endif
static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static bool isSimplyUpdatableQuery(Query *query);
static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
static Query *transformCreateExternalStmt(ParseState *pstate,
CreateExternalStmt *stmt,
List **extras_before,
List **extras_after);
static Query *transformCreateVlabelStmt(ParseState *pstate,
CreateVlabelStmt *stmt,
List **extras_before,
List **extras_after);
static Query *transformCreateElabelStmt(ParseState *pstate,
CreateElabelStmt *stmt,
List **extras_before,
List **extras_after);
static Query *transformCreateGraphStmt(ParseState *pstate,
CreateGraphStmt *stmt,
List **extras_before,
List **extras_after);
static Query *transformCreateForeignStmt(ParseState *pstate,
CreateForeignStmt *stmt,
List **extras_before,
List **extras_after);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
List **extras_before,
List **extras_after);
static void transformColumnDefinition(ParseState *pstate,
CreateStmtContext *cxt,
ColumnDef *column);
static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
Constraint *constraint);
static void transformExtTableConstraint(ParseState *pstate,
CreateStmtContext *cxt,
Constraint *constraint);
static void transformETDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
List **distributedBy, GpPolicy **policyp,
List *options, List *likeDistributedBy,
bool bQuiet, bool iswritable,
bool onmaster);
static void transformDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
List *distributedBy, GpPolicy **policy,
List *options, List *likeDistributedBy,
bool bQuiet);
static void transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
GpPolicy *policy);
static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
bool skipValidation, bool isAddConstraint);
static void applyColumnNames(List *dst, List *src);
static bool isSetopLeaf(SelectStmt *stmt);
static void collectSetopTypes(ParseState *pstate, SelectStmt *stmt,
List **types, List **typmods);
static void getSetColTypes(ParseState *pstate, Node *node, List **colTypes,
List **colTypmods);
static void transformLockingClause(Query *qry, LockingClause *lc);
static void transformConstraintAttrs(List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
static void release_pstate_resources(ParseState *pstate);
static FromExpr *makeFromExpr(List *fromlist, Node *quals);
static bool check_parameter_resolution_walker(
Node *node, check_parameter_resolution_context *context);
static void setQryDistributionPolicy(SelectStmt *stmt, Query *qry);
static List *getLikeDistributionPolicy(InhRelation *e);
static void transformSingleRowErrorHandling(ParseState *pstate,
CreateStmtContext *cxt,
SingleRowErrorDesc *sreh);
static Query *transformGroupedWindows(Query *qry);
static void init_grouped_window_context(grouped_window_ctx *ctx, Query *qry);
static Var *var_for_gw_expr(grouped_window_ctx *ctx, Node *expr, bool force);
static void discard_grouped_window_context(grouped_window_ctx *ctx);
static Node *map_sgr_mutator(Node *node, void *context);
static Node *grouped_window_mutator(Node *node, void *context);
static Alias *make_replacement_alias(Query *qry, const char *aname);
static char *generate_positional_name(AttrNumber attrno);
static List *generate_alternate_vars(Var *var, grouped_window_ctx *ctx);
static List *fillin_encoding(List *list);
static int deparse_partition_rule(Node *pNode, char *outbuf, size_t outsize);
static int partition_range_compare(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, PartitionBy *pBy,
char *at_depth, int partNumber,
char *compare_op, PartitionRangeItem *pRI1,
PartitionRangeItem *pRI2);
static int partition_range_every(ParseState *pstate, PartitionBy *partitionBy,
List *coltypes, char *at_depth,
partValidationState *vstate);
static Datum eval_basic_opexpr(ParseState *pstate, List *oprname, Node *leftarg,
Node *rightarg, bool *typbyval, int16 *typlen,
Oid *restypid, int location);
static Node *make_prule_catalog(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
PartitionElem *pElem, char *at_depth,
char *child_name_str, char *exprBuf,
Node *pWhere);
static Node *make_prule_rulestmt(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
PartitionElem *pElem, char *at_depth,
char *child_name_str, char *exprBuf,
Node *pWhere);
static List *transformAttributeEncoding(List *stenc, CreateStmt *stmt,
CreateStmtContext cxt);
extern char *graphVertexTableName(char *gname,char *vname);
extern char *graphEdgeTableName(char *gname,char *ename);
extern bool parseAndTransformAsGraph(ParseState *pstate, RangeVar *rangeVar);
char *getDefaultFilespace();
/*
* parse_analyze
* Analyze a raw parse tree and transform it to Query form.
*
* If available, pass the source text from which the raw parse tree was
* generated; it's OK to pass NULL if this is not available.
*
* Optionally, information about $n parameter types can be supplied.
* References to $n indexes not defined by paramTypes[] are disallowed.
*
* The result is a List of Query nodes (we need a list since some commands
* produce multiple Queries). Optimizable statements require considerable
* transformation, while many utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node.
*/
List *parse_analyze(Node *parseTree, const char *sourceText, Oid *paramTypes,
int numParams) {
ParseState *pstate = make_parsestate(NULL);
List *result;
pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = paramTypes;
pstate->p_numparams = numParams;
pstate->p_variableparams = false;
result = do_parse_analyze(parseTree, pstate);
free_parsestate(&pstate);
return result;
}
/*
* parse_analyze_varparams
*
* This variant is used when it's okay to deduce information about $n
* symbol datatypes from context. The passed-in paramTypes[] array can
* be modified or enlarged (via repalloc).
*/
List *parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams) {
ParseState *pstate = make_parsestate(NULL);
List *result;
pstate->p_sourcetext = sourceText;
pstate->p_paramtypes = *paramTypes;
pstate->p_numparams = *numParams;
pstate->p_variableparams = true;
result = do_parse_analyze(parseTree, pstate);
*paramTypes = pstate->p_paramtypes;
*numParams = pstate->p_numparams;
free_parsestate(&pstate);
/* make sure all is well with parameter types */
if (*numParams > 0) {
check_parameter_resolution_context context;
context.paramTypes = *paramTypes;
context.numParams = *numParams;
check_parameter_resolution_walker((Node *)result, &context);
}
return result;
}
/*
* parse_sub_analyze
* Entry point for recursively analyzing a sub-statement.
*/
List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState) {
ParseState *pstate = make_parsestate(parentParseState);
List *result;
result = do_parse_analyze(parseTree, pstate);
free_parsestate(&pstate);
return result;
}
static int alter_cmp(const void *a, const void *b) {
Query *qa = *(Query **)a;
Query *qb = *(Query **)b;
AlterTableStmt *stmta = (AlterTableStmt *)qa->utilityStmt;
AlterTableStmt *stmtb = (AlterTableStmt *)qb->utilityStmt;
PartitionBy *pbya = NULL;
PartitionBy *pbyb = NULL;
int len1, len2;
ListCell *lc;
Assert(IsA(stmta, AlterTableStmt));
Assert(IsA(stmtb, AlterTableStmt));
foreach (lc, stmta->cmds) {
AlterTableCmd *cmd = lfirst(lc);
if (cmd->subtype == AT_PartAddInternal) {
pbya = (PartitionBy *)cmd->def;
break;
}
}
foreach (lc, stmtb->cmds) {
AlterTableCmd *cmd = lfirst(lc);
if (cmd->subtype == AT_PartAddInternal) {
pbyb = (PartitionBy *)cmd->def;
break;
}
}
if (pbya && pbyb) {
if (pbya->partDepth < pbyb->partDepth)
return -1;
else if (pbya->partDepth > pbyb->partDepth)
return 1;
else
return 0;
}
len1 = strlen(stmta->relation->relname);
len2 = strlen(stmtb->relation->relname);
if (len1 < len2)
return -1;
else if (len1 > len2)
return 1;
else
/* same size */
return strcmp(stmta->relation->relname, stmtb->relation->relname);
}
/*
* do_parse_analyze
* Workhorse code shared by the above variants of parse_analyze.
*/
static List *do_parse_analyze(Node *parseTree, ParseState *pstate) {
List *result = NIL;
/* Lists to return extra commands from transformation */
List *extras_before = NIL;
List *extras_after = NIL;
List *tmp = NIL;
Query *query;
ListCell *l;
ErrorContextCallback errcontext;
/* CDB: Request a callback in case ereport or elog is called. */
errcontext.callback = parse_analyze_error_callback;
errcontext.arg = pstate;
errcontext.previous = error_context_stack;
error_context_stack = &errcontext;
query = transformStmt(pstate, parseTree, &extras_before, &extras_after);
/* CDB: Pop error context callback stack. */
error_context_stack = errcontext.previous;
/* CDB: All breadcrumbs should have been popped. */
Assert(!pstate->p_breadcrumb.pop);
/* don't need to access result relation any more */
release_pstate_resources(pstate);
foreach (l, extras_before)
result = list_concat(result, parse_sub_analyze(lfirst(l), pstate));
result = lappend(result, query);
foreach (l, extras_after)
tmp = list_concat(tmp, parse_sub_analyze(lfirst(l), pstate));
/*
* If this is the top level query and it is a CreateStmt/CreateExternalStmt
* and it has a partition by clause, reorder the expanded extras_after so
* that AlterTable is able to build the partitioning hierarchy
* better. The problem with the existing list is that for
* subpartitioned tables, the subpartitions will be added to the
* hierarchy before the root, which means we cannot get the parent
* oid of rules.
*
* nefarious: special KeepMe case in cdbpartition.c:atpxPart_validate_spec
*/
if (pstate->parentParseState == NULL && query->utilityStmt &&
(IsA(query->utilityStmt, CreateStmt) &&
((CreateStmt *)query->utilityStmt)->base.partitionBy ||
IsA(query->utilityStmt, CreateExternalStmt) &&
((CreateExternalStmt *)query->utilityStmt)->base.partitionBy)) {
/*
* We just break the statements into two lists: alter statements and
* other statements.
*/
List *alters = NIL;
List *others = NIL;
Query **stmts;
int i = 0;
int j;
foreach (l, tmp) {
Query *q = lfirst(l);
Assert(IsA(q, Query));
if (IsA(q->utilityStmt, AlterTableStmt))
alters = lappend(alters, q);
else
others = lappend(others, q);
}
Assert(list_length(alters));
/*
* Now, sort the ALTER statements so that the deeper partition members
* are processed last.
*/
stmts = palloc(list_length(alters) * sizeof(Query *));
foreach (l, alters)
stmts[i++] = (Query *)lfirst(l);
qsort(stmts, i, sizeof(void *), alter_cmp);
list_free(alters);
alters = NIL;
for (j = 0; j < i; j++) {
AlterTableStmt *n;
alters = lappend(alters, stmts[j]);
n = (AlterTableStmt *)((Query *)stmts[j])->utilityStmt;
}
result = list_concat(result, others);
result = list_concat(result, alters);
} else
result = list_concat(result, tmp);
/*
* Make sure that only the original query is marked original. We have to
* do this explicitly since recursive calls of do_parse_analyze will have
* marked some of the added-on queries as "original". Also mark only the
* original query as allowed to set the command-result tag.
*/
foreach (l, result) {
Query *q = lfirst(l);
if (q == query) {
q->querySource = QSRC_ORIGINAL;
q->canSetTag = true;
} else {
q->querySource = QSRC_PARSER;
q->canSetTag = false;
}
}
return result;
}
static void release_pstate_resources(ParseState *pstate) {
if (pstate->p_target_relation != NULL)
heap_close(pstate->p_target_relation, NoLock);
pstate->p_target_relation = NULL;
pstate->p_target_rangetblentry = NULL;
}
/*
* parse_analyze_error_callback
*
* Called during elog/ereport to add context information to the error message.
*/
static void parse_analyze_error_callback(void *parsestate) {
ParseState *pstate = (ParseState *)parsestate;
ParseStateBreadCrumb *bc;
int location = -1;
/* No-op if errposition has already been set. */
if (geterrposition() > 0) return;
/* NOTICE messages don't need any extra baggage. */
if (elog_getelevel() == NOTICE) return;
/*
* Backtrack through trail of breadcrumbs to find a node with location
* info. A null node ptr tells us to keep quiet rather than give a
* misleading pointer to a token which may be far from the actual problem.
*/
for (bc = &pstate->p_breadcrumb; bc && bc->node; bc = bc->pop) {
location = parse_expr_location((Expr *)bc->node);
if (location >= 0) break;
}
/* Shush the parent query's error callback if we found a location or null */
if (bc && pstate->parentParseState)
pstate->parentParseState->p_breadcrumb.node = NULL;
/* Report approximate offset of error from beginning of statement text. */
if (location >= 0) parser_errposition(pstate, location);
} /* parse_analyze_error_callback */
/*
* transformStmt -
* transform a Parse tree into a Query tree.
*/
static Query *transformStmt(ParseState *pstate, Node *parseTree,
List **extras_before, List **extras_after) {
Query *result = NULL;
switch (nodeTag(parseTree)) {
/*
* Non-optimizable statements
*/
case T_CreateStmt:
result = transformCreateStmt(pstate, (CreateStmt *)parseTree,
extras_before, extras_after);
break;
case T_CreateExternalStmt:
result = transformCreateExternalStmt(
pstate, (CreateExternalStmt *)parseTree, extras_before, extras_after);
break;
case T_CreateVlabelStmt:
result = transformCreateVlabelStmt(
pstate, (CreateVlabelStmt *)parseTree, extras_before, extras_after);
break;
case T_CreateElabelStmt:
result = transformCreateElabelStmt(
pstate, (CreateElabelStmt *)parseTree, extras_before, extras_after);
break;
case T_CreateGraphStmt:
result = transformCreateGraphStmt(
pstate, (CreateGraphStmt *)parseTree, extras_before, extras_after);
break;
case T_CreateForeignStmt:
result = transformCreateForeignStmt(
pstate, (CreateForeignStmt *)parseTree, extras_before, extras_after);
break;
case T_IndexStmt:
result = transformIndexStmt(pstate, (IndexStmt *)parseTree, extras_before,
extras_after);
break;
case T_RuleStmt:
result = transformRuleStmt(pstate, (RuleStmt *)parseTree, extras_before,
extras_after);
break;
case T_ViewStmt:
result = transformViewStmt(pstate, (ViewStmt *)parseTree, extras_before,
extras_after);
break;
case T_ExplainStmt: {
ExplainStmt *n = (ExplainStmt *)parseTree;
result = makeNode(Query);
result->commandType = CMD_UTILITY;
n->query =
transformStmt(pstate, (Node *)n->query, extras_before, extras_after);
result->utilityStmt = (Node *)parseTree;
} break;
case T_CopyStmt: {
CopyStmt *n = (CopyStmt *)parseTree;
/*
* Check if we need to create an error table. If so, add it to the
* before list.
*/
if (n->sreh && ((SingleRowErrorDesc *)n->sreh)->errtable) {
CreateStmtContext cxt;
cxt.blist = NIL;
cxt.alist = NIL;
transformSingleRowErrorHandling(pstate, &cxt,
(SingleRowErrorDesc *)n->sreh);
*extras_before = list_concat(*extras_before, cxt.blist);
}
result = makeNode(Query);
result->commandType = CMD_UTILITY;
if (n->query)
n->query = transformStmt(pstate, (Node *)n->query, extras_before,
extras_after);
result->utilityStmt = (Node *)parseTree;
} break;
case T_AlterTableStmt:
result = transformAlterTableStmt(pstate, (AlterTableStmt *)parseTree,
extras_before, extras_after);
break;
case T_PrepareStmt:
result = transformPrepareStmt(pstate, (PrepareStmt *)parseTree);
break;
case T_ExecuteStmt:
result = transformExecuteStmt(pstate, (ExecuteStmt *)parseTree);
break;
/*
* Optimizable statements
*/
case T_InsertStmt:
result = transformInsertStmt(pstate, (InsertStmt *)parseTree,
extras_before, extras_after);
break;
case T_DeleteStmt:
result = transformDeleteStmt(pstate, (DeleteStmt *)parseTree);
break;
case T_UpdateStmt:
result = transformUpdateStmt(pstate, (UpdateStmt *)parseTree);
break;
case T_SelectStmt: {
SelectStmt *n = (SelectStmt *)parseTree;
if (n->valuesLists)
result = transformValuesClause(pstate, n);
else if (n->op == SETOP_NONE)
result = transformSelectStmt(pstate, n);
else
result = transformSetOperationStmt(pstate, n);
}
break;
case T_DeclareCursorStmt:
result =
transformDeclareCursorStmt(pstate, (DeclareCursorStmt *)parseTree);
break;
default:
/*
* other statements don't require any transformation; just return
* the original parsetree with a Query node plastered on top.
*/
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *)parseTree;
break;
}
/* Mark as original query until we learn differently */
result->querySource = QSRC_ORIGINAL;
result->canSetTag = true;
result->graphEntry = pstate->graphEntry;
/*
* Check that we did not produce too many resnos; at the very least we
* cannot allow more than 2^16, since that would exceed the range of a
* AttrNumber. It seems safest to use MaxTupleAttributeNumber.
*/
if (pstate->p_next_resno - 1 > MaxTupleAttributeNumber)
ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("target lists can have at most %d entries",
MaxTupleAttributeNumber)));
return result;
}
/*
* analyze_requires_snapshot
* Returns true if a snapshot must be set before doing parse
*analysis
* on the given raw parse tree.
*
* Classification here should match transformStmt(); but we also have to
* allow a NULL input (for Parse/Bind of an empty query string).
*/
bool analyze_requires_snapshot(Node *parseTree) {
bool result;
if (parseTree == NULL) return false;
switch (nodeTag(parseTree)) {
/*
* Optimizable statements
*/
case T_InsertStmt:
case T_DeleteStmt:
case T_UpdateStmt:
case T_SelectStmt:
result = true;
break;
/*
* Special cases
*/
case T_DeclareCursorStmt:
/* yes, because it's analyzed just like SELECT */
result = true;
break;
case T_ExplainStmt:
/* yes, because it's analyzed just like SELECT */
result = true;
break;
default:
/* other utility statements don't have any active parse analysis */
result = false;
break;
}
return result;
}
static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
List **extras_before, List **extras_after) {
Query *result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *)stmt;
stmt->query =
transformStmt(pstate, (Node *)stmt->query, extras_before, extras_after);
if (pstate->p_hasDynamicFunction) {
ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg(
"CREATE VIEW statements cannot include calls to "
"dynamically typed function")));
}
/*
* If a list of column names was given, run through and insert these into
* the actual query tree. - thomas 2000-03-08
*
* Outer loop is over targetlist to make it easier to skip junk targetlist
* entries.
*/
if (stmt->aliases != NIL) {
ListCell *alist_item = list_head(stmt->aliases);
ListCell *targetList;
foreach (targetList, stmt->query->targetList) {
TargetEntry *te = (TargetEntry *)lfirst(targetList);
Assert(IsA(te, TargetEntry));
/* junk columns don't get aliases */
if (te->resjunk) continue;
te->resname = pstrdup(strVal(lfirst(alist_item)));
alist_item = lnext(alist_item);
if (alist_item == NULL) break; /* done assigning aliases */
}
if (alist_item != NULL)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"CREATE VIEW specifies more column "
"names than columns")));
}
return result;
}
/*
* transformDeleteStmt -
* transforms a Delete Statement
*/
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) {
Query *qry = makeNode(Query);
Node *qual;
qry->commandType = CMD_DELETE;
/* setup database name for use of magma operations */
MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
char *dbname = stmt->relation->catalogname;
database =
(dbname != NULL) ? pstrdup(dbname) : get_database_name(MyDatabaseId);
MemoryContextSwitchTo(oldContext);
/* set up range table with just the result rel */
qry->resultRelation = setTargetTable(
pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), true,
ACL_DELETE);
if(isGraph)
pstate->p_target_rangetblentry->graphName = stmt->relation->schemaname ?
pstrdup(stmt->relation->schemaname) : NULL;
qry->distinctClause = NIL;
/*
* The USING clause is non-standard SQL syntax, and is equivalent in
* functionality to the FROM list that can be specified for UPDATE. The
* USING keyword is used rather than FROM because FROM is already a
* keyword in the DELETE syntax.
*/
transformFromClause(pstate, stmt->usingClause);
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
/*
* MPP-2506 [insert/update/delete] RETURNING clause not supported:
* We have problems processing the returning clause, so for now we have
* simply removed it and replaced it with an error message.
*/
#ifdef MPP_RETURNING_NOT_SUPPORTED
if (stmt->returningList) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"The RETURNING clause of the DELETE statement is not "
"supported in this version of Greenplum Database.")));
}
#else
qry->returningList = transformReturningList(pstate, stmt->returningList);
#endif
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
/* done building the range table and jointree */
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry);
if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);
return qry;
}
/*
* transformInsertStmt -
* transform an Insert Statement
*/
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after) {
Query *qry = makeNode(Query);
SelectStmt *selectStmt = (SelectStmt *)stmt->selectStmt;
List *exprList = NIL;
bool isGeneralSelect;
List *sub_rtable;
List *sub_relnamespace;
List *sub_varnamespace;
List *icolumns;
List *attrnos;
RangeTblEntry *rte;
RangeTblRef *rtr;
ListCell *icols;
ListCell *attnos;
ListCell *lc;
char *graphName;
qry->commandType = CMD_INSERT;
pstate->p_is_insert = true;
/*
* We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
* VALUES list, or general SELECT input. We special-case VALUES, both for
* efficiency and so we can handle DEFAULT specifications.
*/
isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL);
/*
* If a non-nil rangetable/namespace was passed in, and we are doing
* INSERT/SELECT, arrange to pass the rangetable/namespace down to the
* SELECT. This can only happen if we are inside a CREATE RULE, and in
* that case we want the rule's OLD and NEW rtable entries to appear as
* part of the SELECT's rtable, not as outer references for it. (Kluge!)
* The SELECT's joinlist is not affected however. We must do this before
* adding the target table to the INSERT's rtable.
*/
if (isGeneralSelect) {
sub_rtable = pstate->p_rtable;
pstate->p_rtable = NIL;
sub_relnamespace = pstate->p_relnamespace;
pstate->p_relnamespace = NIL;
sub_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = NIL;
} else {
sub_rtable = NIL; /* not used, but keep compiler quiet */
sub_relnamespace = NIL;
sub_varnamespace = NIL;
}
graphName = stmt->relation->schemaname ? pstrdup(stmt->relation->schemaname) : NULL;
int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
database =
(stmt->relation->catalogname != NULL) ? pstrdup(stmt->relation->catalogname) : get_database_name(MyDatabaseId);
MemoryContextSwitchTo(oldContext);
/*
* Must get write lock on INSERT target table before scanning SELECT, else
* we will grab the wrong kind of initial lock if the target table is also
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
qry->resultRelation =
setTargetTable(pstate, stmt->relation, false, false, ACL_INSERT);
if(isGraph)
pstate->p_target_rangetblentry->graphName = graphName;
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
Assert(list_length(icolumns) == list_length(attrnos));
/*
* Determine which variant of INSERT we have.
*/
if (selectStmt == NULL) {
/*
* We have INSERT ... DEFAULT VALUES. We can handle this case by
* emitting an empty targetlist --- all columns will be defaulted when
* the planner expands the targetlist.
*/
exprList = NIL;
} else if (isGeneralSelect) {
/*
* We make the sub-pstate a child of the outer pstate so that it can
* see any Param definitions supplied from above. Since the outer
* pstate's rtable and namespace are presently empty, there are no
* side-effects of exposing names the sub-SELECT shouldn't be able to
* see.
*/
ParseState *sub_pstate = make_parsestate(pstate);
Query *selectQuery;
/*
* Process the source SELECT.
*
* It is important that this be handled just like a standalone SELECT;
* otherwise the behavior of SELECT within INSERT might be different
* from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
* bugs of just that nature...)
*/
sub_pstate->p_rtable = sub_rtable;
sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace;
/*
* Note: we are not expecting that extras_before and extras_after are
* going to be used by the transformation of the SELECT statement.
*/
selectQuery = transformStmt(sub_pstate, stmt->selectStmt, extras_before,
extras_after);
release_pstate_resources(sub_pstate);
free_parsestate(&sub_pstate);
Assert(IsA(selectQuery, Query));
Assert(selectQuery->commandType == CMD_SELECT);
if (selectQuery->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT ... SELECT cannot specify INTO"),
parser_errposition(
pstate, exprLocation((Node *)selectQuery->intoClause))));
/*
* Make the source be a subquery in the INSERT's rangetable, and add
* it to the INSERT's joinlist.
*/
rte = addRangeTableEntryForSubquery(pstate, selectQuery,
makeAlias("*SELECT*", NIL), false);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
/*----------
* Generate an expression list for the INSERT that selects all the
* non-resjunk columns from the subquery. (INSERT's tlist must be
* separate from the subquery's tlist because we may add columns,
* insert datatype coercions, etc.)
*
* Const and Param nodes of type UNKNOWN in the SELECT's targetlist
* no longer need special treatment here. They'll be assigned proper
* types later by coerce_type() upon assignment to the target columns.
* Otherwise this fails: INSERT INTO foo SELECT 'bar', ... FROM baz
*----------
*/
expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
/* Prepare row for assignment to target table */
exprList =
transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
} else if (list_length(selectStmt->valuesLists) > 1) {
/*
* Process INSERT ... VALUES with multiple VALUES sublists. We
* generate a VALUES RTE holding the transformed expression lists, and
* build up a targetlist containing Vars that reference the VALUES
* RTE.
*/
List *exprsLists = NIL;
int sublist_length = -1;
foreach (lc, selectStmt->valuesLists) {
List *sublist = (List *)lfirst(lc);
/* CDB: In case of error, note which sublist is involved. */
pstate->p_breadcrumb.node = (Node *)sublist;
/* Do basic expression transformation (same as a ROW() expr) */
sublist = transformExpressionList(pstate, sublist);
/*
* All the sublists must be the same length, *after*
* transformation (which might expand '*' into multiple items).
* The VALUES RTE can't handle anything different.
*/
if (sublist_length < 0) {
/* Remember post-transformation length of first sublist */
sublist_length = list_length(sublist);
} else if (sublist_length != list_length(sublist)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("VALUES lists must all be the same length"),
parser_errposition(pstate, exprLocation((Node *)sublist))));
}
/* Prepare row for assignment to target table */
sublist =
transformInsertRow(pstate, sublist, stmt->cols, icolumns, attrnos);
exprsLists = lappend(exprsLists, sublist);
}
/* CDB: Clear error location. */
pstate->p_breadcrumb.node = NULL;
/*
* There mustn't have been any table references in the expressions,
* else strange things would happen, like Cartesian products of those
* tables with the VALUES list ...
*/
if (pstate->p_joinlist != NIL)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references")));
/*
* Another thing we can't currently support is NEW/OLD references in
* rules --- seems we'd need something like SQL99's LATERAL construct
* to ensure that the values would be available while evaluating the
* VALUES RTE. This is a shame. FIXME
*/
if (list_length(pstate->p_rtable) != 1 &&
contain_vars_of_level((Node *)exprsLists, 0))
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead.")));
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
/*
* Generate list of Vars referencing the RTE
*/
expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
} else {
/*----------
* Process INSERT ... VALUES with a single VALUES sublist.
* We treat this separately for efficiency and for historical
* compatibility --- specifically, allowing table references,
* such as
* INSERT INTO foo VALUES(bar.*)
*
* The sublist is just computed directly as the Query's targetlist,
* with no VALUES RTE. So it works just like SELECT without FROM.
*----------
*/
List *valuesLists = selectStmt->valuesLists;
Assert(list_length(valuesLists) == 1);
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate, (List *)linitial(valuesLists));
/* Prepare row for assignment to target table */
exprList =
transformInsertRow(pstate, exprList, stmt->cols, icolumns, attrnos);
}
/*
* Generate query's target list using the computed list of expressions.
*/
qry->targetList = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach (lc, exprList) {
Expr *expr = (Expr *)lfirst(lc);
ResTarget *col;
TargetEntry *tle;
col = (ResTarget *)lfirst(icols);
Assert(IsA(col, ResTarget));
tle =
makeTargetEntry(expr, (AttrNumber)lfirst_int(attnos), col->name, false);
qry->targetList = lappend(qry->targetList, tle);
icols = lnext(icols);
attnos = lnext(attnos);
}
/*
* MPP-2506 [insert/update/delete] RETURNING clause not supported:
* We have problems processing the returning clause, so for now we have
* simply removed it and replaced it with an error message.
*/
#ifdef MPP_RETURNING_NOT_SUPPORTED
if (stmt->returningList) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"The RETURNING clause of the INSERT statement is not "
"supported in this version of Greenplum Database.")));
}
#else
/*
* If we have a RETURNING clause, we need to add the target relation to
* the query namespace before processing it, so that Var references in
* RETURNING will work. Also, remove any namespace entries added in a
* sub-SELECT or VALUES list.
*/
if (stmt->returningList) {
pstate->p_relnamespace = NIL;
pstate->p_varnamespace = NIL;
addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true);
qry->returningList = transformReturningList(pstate, stmt->returningList);
}
#endif
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
/* done building the range table and jointree */
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES")));
if (pstate->p_hasWindFuncs)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in VALUES")));
return qry;
}
/*
* Prepare an INSERT row for assignment to the target table.
*
* The row might be either a VALUES row, or variables referencing a
* sub-SELECT output.
*/
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos) {
List *result;
ListCell *lc;
ListCell *icols;
ListCell *attnos;
/*
* Check length of expr list. It must not have more expressions than
* there are target columns. We allow fewer, but only if no explicit
* columns list was given (the remaining columns are implicitly
* defaulted). Note we must check this *after* transformation because
* that could expand '*' into multiple items.
*/
if (list_length(exprlist) > list_length(icolumns))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more expressions than target columns"),
errOmitLocation(true)));
if (stmtcols != NIL && list_length(exprlist) < list_length(icolumns))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT has more target columns than expressions"),
errOmitLocation(true)));
/*
* Prepare columns for assignment to target table.
*/
result = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
foreach (lc, exprlist) {
Expr *expr = (Expr *)lfirst(lc);
ResTarget *col;
col = (ResTarget *)lfirst(icols);
Assert(IsA(col, ResTarget));
expr = transformAssignedExpr(pstate, expr, col->name, lfirst_int(attnos),
col->indirection, col->location);
result = lappend(result, expr);
icols = lnext(icols);
attnos = lnext(attnos);
}
return result;
}
/*
* Tells the caller if CO is explicitly disabled, to handle cases where we
* want to ignore encoding clauses in partition expansion.
*
* This is an ugly special case that backup expects to work and since we've got
* tonnes of dumps out there and the possibility that users have learned this
* grammar from them, we must continue to support it.
*/
static bool co_explicitly_disabled(List *opts) {
ListCell *lc;
foreach (lc, opts) {
DefElem *el = lfirst(lc);
char *arg = NULL;
/* Arguement will be a Value */
if (!el->arg) {
continue;
}
bool need_free_arg = false;
arg = defGetString(el, &need_free_arg);
bool result = false;
if (pg_strcasecmp("appendonly", el->defname) == 0 &&
pg_strcasecmp("false", arg) == 0) {
result = true;
} else if (pg_strcasecmp("orientation", el->defname) == 0 &&
pg_strcasecmp("column", arg) != 0) {
result = true;
}
if (need_free_arg) {
pfree(arg);
arg = NULL;
}
AssertImply(need_free_arg, NULL == arg);
if (result) {
return true;
}
}
return false;
}
/*
* See if two encodings attempt to see the same parameters. If test_conflicts is
* true, allow setting the same value, but the setting must be identical.
*/
static bool encodings_overlap(List *a, List *b, bool test_conflicts) {
ListCell *lca;
foreach (lca, a) {
ListCell *lcb;
DefElem *ela = lfirst(lca);
foreach (lcb, b) {
DefElem *elb = lfirst(lcb);
if (pg_strcasecmp(ela->defname, elb->defname) == 0) {
if (test_conflicts) {
if (!ela->arg && !elb->arg)
return true;
else if (!ela->arg || !elb->arg) {
/* skip */
} else {
bool need_free_ela = false;
bool need_free_elb = false;
char *ela_str = defGetString(ela, &need_free_ela);
char *elb_str = defGetString(elb, &need_free_elb);
int result = pg_strcasecmp(ela_str, elb_str);
// free ela_str, elb_str if it is initialized via TypeNameToString
if (need_free_ela) {
pfree(ela_str);
ela_str = NULL;
}
if (need_free_elb) {
pfree(elb_str);
elb_str = NULL;
}
AssertImply(need_free_ela, NULL == ela_str);
AssertImply(need_free_elb, NULL == elb_str);
if (result != 0) {
return true;
}
}
} else
return true;
}
}
}
return false;
}
/*
* Transform and validate the actual encoding clauses.
*
* We need tell the underlying system that these are AO/CO tables too,
* hence the concatenation of the extra elements.
*/
List *transformStorageEncodingClause(List *options) {
Datum d;
List *extra =
list_make1(makeDefElem("appendonly", (Node *)makeString("true")));
// makeDefElem("orientation",
// (Node
//*)makeString("column")));
/* add defaults for missing values */
options = fillin_encoding(options);
/*
* The following two statements validate that the encoding clause is well
* formed.
*/
d = transformRelOptions(PointerGetDatum(NULL), list_concat(extra, options),
false, false);
(void)heap_reloptions(0, d, true);
return options;
}
/*
* Validate the sanity of column reference storage clauses.
*
* 1. Ensure that we only refer to columns that exist.
* 2. Ensure that each column is referenced either zero times or once.
*/
static void validateColumnStorageEncodingClauses(List *stenc,
CreateStmt *stmt) {
ListCell *lc;
struct HTAB *ht = NULL;
struct colent {
char colname[NAMEDATALEN];
int count;
} *ce = NULL;
if (!stenc) return;
/* Generate a hash table for all the columns */
foreach (lc, stmt->base.tableElts) {
Node *n = lfirst(lc);
if (IsA(n, ColumnDef)) {
ColumnDef *c = (ColumnDef *)n;
char *colname;
bool found = false;
size_t n = NAMEDATALEN - 1 < strlen(c->colname) ? NAMEDATALEN - 1
: strlen(c->colname);
colname = palloc0(NAMEDATALEN);
MemSet(colname, 0, NAMEDATALEN);
memcpy(colname, c->colname, n);
colname[n] = '\0';
if (!ht) {
HASHCTL cacheInfo;
int cacheFlags;
memset(&cacheInfo, 0, sizeof(cacheInfo));
cacheInfo.keysize = NAMEDATALEN;
cacheInfo.entrysize = sizeof(*ce);
cacheFlags = HASH_ELEM;
ht = hash_create("column info cache", list_length(stmt->base.tableElts),
&cacheInfo, cacheFlags);
}
ce = hash_search(ht, colname, HASH_ENTER, &found);
/*
* The user specified a duplicate column name. We check duplicate
* column names VERY late (under MergeAttributes(), which is called
* by DefineRelation(). For the specific case here, it is safe to
* call out that this is a duplicate. We don't need to delay until
* we look at inheritance.
*/
if (found) {
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" duplicated", colname),
errOmitLocation(true)));
}
ce->count = 0;
}
}
/*
* If the table has no columns -- usually in the partitioning case -- then
* we can short circuit.
*/
if (!ht) return;
/*
* All column reference storage directives without the DEFAULT
* clause should refer to real columns.
*/
foreach (lc, stenc) {
ColumnReferenceStorageDirective *c = lfirst(lc);
Insist(IsA(c, ColumnReferenceStorageDirective));
if (c->deflt)
continue;
else {
bool found = false;
char colname[NAMEDATALEN];
size_t collen = strlen(strVal(c->column));
size_t n = NAMEDATALEN - 1 < collen ? NAMEDATALEN - 1 : collen;
MemSet(colname, 0, NAMEDATALEN);
memcpy(colname, strVal(c->column), n);
colname[n] = '\0';
ce = hash_search(ht, colname, HASH_FIND, &found);
if (!found) elog(ERROR, "column \"%s\" does not exist", colname);
ce->count++;
if (ce->count > 1)
elog(ERROR,
"column \"%s\" referenced in more than one "
"COLUMN ENCODING clause",
colname);
}
}
hash_destroy(ht);
}
/*
* Find the column reference storage encoding clause for `column'.
*
* This is called by transformAttributeEncoding() in a loop but stenc should be
* quite small in practice.
*/
static ColumnReferenceStorageDirective *find_crsd(Value *column, List *stenc) {
ListCell *lc;
foreach (lc, stenc) {
ColumnReferenceStorageDirective *c = lfirst(lc);
if (c->deflt == false && equal(column, c->column)) return c;
}
return NULL;
}
List *TypeNameGetStorageDirective(TypeName *typname) {
HeapTuple tuple;
cqContext *pcqCtx;
Oid typid = typenameTypeId(NULL, typname);
List *out = NIL;
/* XXX XXX: SELECT typoptions */
pcqCtx = caql_beginscan(NULL, cql("SELECT * FROM pg_type_encoding "
" WHERE typid = :1 ",
ObjectIdGetDatum(typid)));
tuple = caql_getnext(pcqCtx);
if (HeapTupleIsValid(tuple)) {
Datum options;
bool isnull;
options = caql_getattr(pcqCtx, Anum_pg_type_encoding_typoptions, &isnull);
Insist(!isnull);
out = untransformRelOptions(options);
}
caql_endscan(pcqCtx);
return out;
}
/*
* Make a default column storage directive from a WITH clause
* Ignore options in the WITH clause that don't appear in
* storage_directives for column-level compression.
*/
List *form_default_storage_directive(List *enc) {
List *out = NIL;
ListCell *lc;
bool parquetTable = false;
bool pagesizeSet = false;
bool rowgroupsizeSet = false;
bool need_free_arg = false;
foreach (lc, enc) {
DefElem *el = lfirst(lc);
if (!el->defname) out = lappend(out, copyObject(el));
if (pg_strcasecmp("appendonly", el->defname) == 0) continue;
if (pg_strcasecmp("oids", el->defname) == 0) continue;
if (pg_strcasecmp("errortable", el->defname) == 0) continue;
if (pg_strcasecmp("fillfactor", el->defname) == 0) continue;
if (pg_strcasecmp("tablename", el->defname) == 0) continue;
if (pg_strcasecmp("orientation", el->defname) == 0) {
if (el->arg == NULL)
insist_log(
false,
"syntax not correct, orientation should has corresponding value");
if (pg_strcasecmp("column", defGetString(el, &need_free_arg)) == 0) {
continue;
}
if (pg_strcasecmp("parquet", defGetString(el, &need_free_arg)) == 0)
parquetTable = true;
}
if (pg_strcasecmp("pagesize", el->defname) == 0) pagesizeSet = true;
if (pg_strcasecmp("rowgroupsize", el->defname) == 0) rowgroupsizeSet = true;
out = lappend(out, copyObject(el));
}
/* If parquet table, but pagesize/rowgroupsize not set, should set them to
* default value*/
if (parquetTable) {
if (!pagesizeSet) {
DefElem *f = makeNode(DefElem);
f->defname = "pagesize";
f->arg = (Node *)makeInteger(DEFAULT_PARQUET_PAGE_SIZE_PARTITION);
out = lappend(out, f);
}
if (!rowgroupsizeSet) {
DefElem *f = makeNode(DefElem);
f->defname = "rowgroupsize";
f->arg = (Node *)makeInteger(DEFAULT_PARQUET_ROWGROUP_SIZE_PARTITION);
out = lappend(out, f);
}
}
return out;
}
static List *transformAttributeEncoding(List *stenc, CreateStmt *stmt,
CreateStmtContext cxt) {
ListCell *lc;
bool found_enc = stenc != NIL;
ColumnReferenceStorageDirective *deflt = NULL;
List *newenc = NIL;
List *tmpenc;
#define UNSUPPORTED_ORIENTATION_ERROR() \
ereport( \
ERROR, \
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
errmsg("ENCODING clause only supported with column oriented tables")))
/*
* The migrator puts lots of confusing things in the WITH() clause. We never
* expect to do AOCO table creation during upgrade so bail out.
*/
if (gp_upgrade_mode) {
return NIL;
}
/* We only support the attribute encoding clause on AOCS tables */
if (stenc) UNSUPPORTED_ORIENTATION_ERROR();
/* get the default clause, if there is one. */
foreach (lc, stenc) {
ColumnReferenceStorageDirective *c = lfirst(lc);
Insist(IsA(c, ColumnReferenceStorageDirective));
if (c->deflt) {
/*
* Some quick validation: there should only be one default
* clause
*/
if (deflt)
elog(ERROR, "only one default column encoding may be specified");
else {
deflt = c;
deflt->encoding = transformStorageEncodingClause(deflt->encoding);
/*
* The default encoding and the with clause better not
* try and set the same options!
*/
if (encodings_overlap(stmt->base.options, c->encoding, false))
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"DEFAULT COLUMN ENCODING clause cannot "
"override values set in WITH clause")));
}
}
}
/*
* If no default has been specified, we might create one out of the
* WITH clause.
*/
if (!deflt) {
tmpenc = form_default_storage_directive(stmt->base.options);
} else {
tmpenc = NIL;
}
if (tmpenc) {
deflt = makeNode(ColumnReferenceStorageDirective);
deflt->deflt = true;
deflt->encoding = transformStorageEncodingClause(tmpenc);
}
/*
* Loop over all columns. If a column has a column reference storage clause
* -- i.e., COLUMN name ENCODING () -- apply that. Otherwise, apply the
* default.
*/
foreach (lc, cxt.columns) {
Node *n = lfirst(lc);
ColumnDef *d = (ColumnDef *)n;
ColumnReferenceStorageDirective *c =
makeNode(ColumnReferenceStorageDirective);
Insist(IsA(d, ColumnDef));
c->column = makeString(pstrdup(d->colname));
if (d->encoding) {
found_enc = true;
c->encoding = d->encoding;
c->encoding = transformStorageEncodingClause(c->encoding);
} else {
/*
* No explicit encoding clause but we may still have a
* clause if
* i. There's a column reference storage directive for this
* column
* ii. There's a default column encoding
* iii. There's a default for the type.
*
* If none of these is the case, we set an 'empty' encoding
* clause.
*/
/*
* We use stenc here -- the storage encoding directives
* gleaned from the table elements list because we know
* there's nothing to look at in new_enc, since we're
* generating that
*/
ColumnReferenceStorageDirective *s = find_crsd(c->column, stenc);
if (s) {
s->encoding = transformStorageEncodingClause(s->encoding);
newenc = lappend(newenc, s);
continue;
}
/* ... and so we beat on, boats against the current... */
if (deflt) {
c->encoding = copyObject(deflt->encoding);
} else {
List *te = TypeNameGetStorageDirective(d->typname);
if (te) {
c->encoding = copyObject(te);
} else
c->encoding = default_column_encoding_clause();
}
}
newenc = lappend(newenc, c);
}
/* Check again incase we expanded a some column encoding clauses */
if (found_enc)
UNSUPPORTED_ORIENTATION_ERROR();
else
return NULL;
validateColumnStorageEncodingClauses(newenc, stmt);
return newenc;
}
Query *transformCreateStmtImpl(ParseState *pstate,
CreateStmt *stmt,
List **extras_before,
List **extras_after) {
return transformCreateStmt(pstate, stmt, extras_before, extras_after);
}
/*
* transformCreateStmt -
* transforms the "create table" statement
* SQL92 allows constraints to be scattered all over, so thumb through
* the columns and collect all constraints into one place.
* If there are any implied indices (e.g. UNIQUE or PRIMARY KEY)
* then expand those into multiple IndexStmt blocks.
* - thomas 1997-12-02
*/
Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
List **extras_before, List **extras_after) {
CreateStmtContext cxt;
Query *q;
ListCell *elements;
List *likeDistributedBy = NIL;
bool bQuiet = false; /* shut up transformDistributedBy messages */
List *stenc = NIL; /* column reference storage encoding clauses */
cxt.stmtType = "CREATE TABLE";
cxt.isExternalTable = false;
cxt.relation = stmt->base.relation;
cxt.inhRelations = stmt->base.inhRelations;
cxt.isalter = false;
cxt.isaddpart = stmt->base.is_add_part;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
cxt.dlist = NIL; /* for deferred analysis requiring the created table */
cxt.pkey = NULL;
cxt.exttypedesc = NULL;
cxt.hasoids = interpretOidsOption(stmt->base.options);
stmt->policy = NULL;
/* Disallow inheritance in combination with partitioning. */
if (stmt->base.inhRelations &&
(stmt->base.partitionBy || stmt->base.is_part_child)) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot mix inheritance with partitioning")));
}
/* Only on top-most partitioned tables. */
if (stmt->base.partitionBy && !stmt->base.is_part_child) {
fixCreateStmtForPartitionedTable(&stmt->base);
}
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis.
*/
foreach (elements, stmt->base.tableElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_ColumnDef:
transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
break;
case T_Constraint:
transformTableConstraint(pstate, &cxt, (Constraint *)element);
break;
case T_FkConstraint:
/* No pre-transformation needed */
cxt.fkconstraints = lappend(cxt.fkconstraints, element);
break;
case T_InhRelation: {
bool isBeginning = (cxt.columns == NIL);
transformInhRelation(pstate, &cxt, (InhRelation *)element, false);
if (Gp_role == GP_ROLE_DISPATCH && isBeginning &&
stmt->base.distributedBy == NIL && stmt->base.inhRelations == NIL &&
stmt->policy == NULL) {
likeDistributedBy = getLikeDistributionPolicy((InhRelation *)element);
}
} break;
case T_ColumnReferenceStorageDirective:
/* processed below in transformAttributeEncoding() */
stenc = lappend(stenc, element);
break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
break;
}
}
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
Assert(stmt->base.constraints == NIL);
/*
* Postprocess constraints that give rise to index definitions.
*/
transformIndexConstraints(pstate, &cxt,
stmt->base.is_add_part || stmt->is_split_part);
/*
* Carry any deferred analysis statements forward. Added for MPP-13750
* but should also apply to the similar case involving simple inheritance.
*/
if (cxt.dlist) {
stmt->deferredStmts = list_concat(stmt->deferredStmts, cxt.dlist);
cxt.dlist = NIL;
}
/*
* Postprocess foreign-key constraints.
* But don't cascade FK constraints to parts, yet.
*/
if (!stmt->base.is_part_child)
transformFKConstraints(pstate, &cxt, true, false);
/*
* Analyze attribute encoding clauses.
*
* Partitioning configurations may have things like:
*
* CREATE TABLE ...
* ( a int ENCODING (...))
* WITH (appendonly=true, orientation=column)
* PARTITION BY ...
* (PARTITION ... WITH (appendonly=false));
*
* We don't want to throw an error when we try to apply the ENCODING clause
* to the partition which the user wants to be non-AO. Just ignore it
* instead.
*/
if (stmt->base.is_part_child) {
if (co_explicitly_disabled(stmt->base.options) || !stenc)
stmt->attr_encodings = NIL;
else {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"ENCODING clause only supported with "
"column oriented partitioned tables")));
}
} else
stmt->attr_encodings = transformAttributeEncoding(stenc, stmt, cxt);
/*
* Postprocess Greenplum Database distribution columns
*/
if (stmt->base.is_part_child ||
(stmt->base.partitionBy &&
(
/* be very quiet if set subpartn template */
(((PartitionBy *)(stmt->base.partitionBy))->partQuiet ==
PART_VERBO_NOPARTNAME) ||
(
/* quiet for partitions of depth > 0 */
(((PartitionBy *)(stmt->base.partitionBy))->partDepth != 0) &&
(((PartitionBy *)(stmt->base.partitionBy))->partQuiet !=
PART_VERBO_NORMAL)))))
bQuiet = true; /* silence distro messages for partitions */
transformDistributedBy(pstate, &cxt, stmt->base.distributedBy, &stmt->policy,
stmt->base.options, likeDistributedBy, bQuiet);
/*
* Process table partitioning clause
*/
transformPartitionBy(pstate, &cxt, &stmt->base, stmt->base.partitionBy,
stmt->policy);
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
stmt->base.tableElts = cxt.columns;
stmt->base.constraints = cxt.ckconstraints;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
enum PreDefinedFormatterOptionVALTYPE {
PREDEF_FMTOPT_VAL_NO,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMTOPT_VAL_SIGNEDINTEGER,
PREDEF_FMTOPT_VAL_COLNAMELIST
};
enum PreDefinedFormatterOptionID {
PREDEF_FMT_OPT_ID_DELIMITER,
PREDEF_FMT_OPT_ID_NULL,
PREDEF_FMT_OPT_ID_HEADER,
PREDEF_FMT_OPT_ID_QUOTE,
PREDEF_FMT_OPT_ID_ESCAPE,
PREDEF_FMT_OPT_ID_FORCENOTNULL,
PREDEF_FMT_OPT_ID_FORCEQUOTE,
PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS,
PREDEF_FMT_OPT_ID_NEWLINE,
PREDEF_FMT_OPT_ID_UNPREDEFINED,
PREDEF_FMT_OPT_ID_ILLEGAL
};
typedef struct PreDefinedFormatterOption {
char keyword[3][32];
int nKeyword;
bool hasValue;
enum PreDefinedFormatterOptionVALTYPE valueType;
enum PreDefinedFormatterOptionID optID;
} PreDefinedFormatterOption;
#define PREDEF_FMTOPT_SIZE 9
enum PreDefinedFormatterOptionID MatchExternalRelationFormatterOption(
PreDefinedFormatterOption *options, ListCell *head) {
ListCell *p1 = head;
ListCell *p2 = head->next;
ListCell *p3 = p2 == NULL ? NULL : p2->next;
ListCell *p4 = p3 == NULL ? NULL : p3->next;
DefElem *de1 = (DefElem *)lfirst(p1);
DefElem *de2 = p2 == NULL ? NULL : (DefElem *)lfirst(p2);
DefElem *de3 = p3 == NULL ? NULL : (DefElem *)lfirst(p3);
DefElem *de4 = p4 == NULL ? NULL : (DefElem *)lfirst(p4);
if (strcmp("#ident", de1->defname) != 0) {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* must start with a #ident elem */
}
for (int i = 0; i < PREDEF_FMTOPT_SIZE; ++i) {
PreDefinedFormatterOption *pdOpt = &(options[i]);
if (pdOpt->nKeyword == 1 &&
strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) == 0) {
if (!options[i].hasValue) {
return options[i].optID; /* Got no value option */
} else if (p2 == NULL) {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
} else if (((strcmp("#string", de2->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
((strcmp("#int", de2->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
((strcmp("#collist", de2->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
((strcmp("#ident", de2->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
return options[i].optID; /* Got option having one value */
} else {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
}
} else if (pdOpt->nKeyword == 2 && de2 != NULL &&
strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) ==
0 &&
strcasecmp(pdOpt->keyword[1], ((Value *)(de2->arg))->val.str) ==
0) {
if (!options[i].hasValue) {
return options[i].optID; /* got no value option */
} else if (de3 == NULL) {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
} else if (((strcmp("#string", de3->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
((strcmp("#int", de3->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
((strcmp("#collist", de3->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
((strcmp("#ident", de3->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
return options[i].optID; /* Got option having one value */
} else {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
}
} else if (pdOpt->nKeyword == 3 && de2 != NULL && de3 != NULL &&
strcasecmp(pdOpt->keyword[0], ((Value *)(de1->arg))->val.str) ==
0 &&
strcasecmp(pdOpt->keyword[1], ((Value *)(de2->arg))->val.str) ==
0 &&
strcasecmp(pdOpt->keyword[2], ((Value *)(de3->arg))->val.str) ==
0) {
if (!options[i].hasValue) {
return options[i].optID; /* got no value option */
} else if (de4 == NULL) {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value field */
} else if (((strcmp("#string", de4->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_STRING)) ||
((strcmp("#int", de4->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_SIGNEDINTEGER)) ||
((strcmp("#collist", de4->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST)) ||
((strcmp("#ident", de4->defname) == 0) &&
(options[i].valueType == PREDEF_FMTOPT_VAL_COLNAMELIST))) {
return options[i].optID; /* Got option having one value */
} else {
return PREDEF_FMT_OPT_ID_ILLEGAL; /* no expected value type */
}
}
}
/*
* We expect user defined special options which should be consumed
* further by customized formatter.
*/
return PREDEF_FMT_OPT_ID_UNPREDEFINED;
}
void recognizeExternalRelationFormatterOptions(
CreateExternalStmt *createExtStmt) {
PreDefinedFormatterOption options[PREDEF_FMTOPT_SIZE] = {
{{"delimiter", "", ""},
1,
true,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMT_OPT_ID_DELIMITER},
{{"null", "", ""},
1,
true,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMT_OPT_ID_NULL},
{{"header", "", ""},
1,
false,
PREDEF_FMTOPT_VAL_NO,
PREDEF_FMT_OPT_ID_HEADER},
{{"quote", "", ""},
1,
true,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMT_OPT_ID_QUOTE},
{{"escape", "", ""},
1,
true,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMT_OPT_ID_ESCAPE},
{{"force", "not", "null"},
3,
true,
PREDEF_FMTOPT_VAL_COLNAMELIST,
PREDEF_FMT_OPT_ID_FORCENOTNULL},
{{"force", "quote", ""},
2,
true,
PREDEF_FMTOPT_VAL_COLNAMELIST,
PREDEF_FMT_OPT_ID_FORCEQUOTE},
{{"fill", "missing", "fields"},
3,
false,
PREDEF_FMTOPT_VAL_NO,
PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS},
{{"newline", "", ""},
1,
true,
PREDEF_FMTOPT_VAL_STRING,
PREDEF_FMT_OPT_ID_NEWLINE}};
List *newOpts = NULL;
ListCell *optCell = list_head(createExtStmt->base.options);
/* Add restriction of error lines */
if (createExtStmt->sreh != NULL) {
/* Handle error table specification and reject number per segment */
SingleRowErrorDesc *errDesc = (SingleRowErrorDesc *)createExtStmt->sreh;
if (errDesc->rejectlimit > 0 && errDesc->is_hdfs_protocol_text) {
newOpts = lappend(
newOpts,
makeDefElem("reject_limit", makeInteger(errDesc->rejectlimit)));
if (errDesc->hdfsLoc)
newOpts =
lappend(newOpts,
makeDefElem("err_table",
(Node *)makeString(pstrdup(errDesc->hdfsLoc))));
}
}
while (optCell != NULL) {
/* Try a match now. */
enum PreDefinedFormatterOptionID id =
MatchExternalRelationFormatterOption(options, optCell);
switch (id) {
case PREDEF_FMT_OPT_ID_DELIMITER: {
DefElem *de = (DefElem *)lfirst(optCell->next);
Value *v = (Value *)(de->arg);
DefElem *newde =
makeDefElem("delimiter", (Node *)makeString(v->val.str));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next;
break;
}
case PREDEF_FMT_OPT_ID_NULL: {
DefElem *de = (DefElem *)lfirst(optCell->next);
Value *v = (Value *)(de->arg);
DefElem *newde = makeDefElem("null", (Node *)makeString(v->val.str));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next;
break;
}
case PREDEF_FMT_OPT_ID_HEADER: {
DefElem *newde = makeDefElem("header", (Node *)makeInteger(TRUE));
newOpts = lappend(newOpts, newde);
optCell = optCell->next;
break;
}
case PREDEF_FMT_OPT_ID_QUOTE: {
DefElem *de = (DefElem *)lfirst(optCell->next);
Value *v = (Value *)(de->arg);
DefElem *newde = makeDefElem("quote", (Node *)makeString(v->val.str));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next;
break;
}
case PREDEF_FMT_OPT_ID_ESCAPE: {
DefElem *de = (DefElem *)lfirst(optCell->next);
Value *v = (Value *)(de->arg);
DefElem *newde = makeDefElem("escape", (Node *)makeString(v->val.str));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next;
break;
}
case PREDEF_FMT_OPT_ID_FORCENOTNULL: {
DefElem *newde = NULL;
DefElem *de = (DefElem *)lfirst(optCell->next->next->next);
if (strcmp("#ident", de->defname) == 0) {
/*
* The case there is only one column name which is recognized
* as a ident string.
*/
Value *v = (Value *)(de->arg);
List *collist = list_make1(makeString(v->val.str));
newde = makeDefElem("force_notnull", (Node *)collist);
} else {
/*
* There are multiple column names in a list already
* recognized by parser.
*/
List *collist = NULL;
ListCell *colCell = NULL;
foreach (colCell, (List *)(de->arg)) {
collist = lappend(collist,
makeString(((Value *)lfirst(colCell))->val.str));
elog(LOG, "recognized column list colname:%s",
((Value *)lfirst(colCell))->val.str);
}
newde = makeDefElem("force_notnull", (Node *)collist);
/* TODO: check where the old instance is freed */
}
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next->next->next;
break;
}
case PREDEF_FMT_OPT_ID_FORCEQUOTE: {
DefElem *newde = NULL;
DefElem *de = (DefElem *)lfirst(optCell->next->next);
if (strcmp("#ident", de->defname) == 0) {
/*
* The case there is only one column name which is recognized
* as a ident string.
*/
Value *v = (Value *)(de->arg);
List *collist = list_make1(makeString(v->val.str));
newde = makeDefElem("force_quote", (Node *)collist);
} else {
/*
* There are multiple column names in a list already
* recognized by parser.
*/
List *collist = NULL;
ListCell *colCell = NULL;
foreach (colCell, (List *)(de->arg)) {
collist = lappend(collist,
makeString(((Value *)lfirst(colCell))->val.str));
elog(LOG, "recognized column list colname:%s",
((Value *)lfirst(colCell))->val.str);
}
newde = makeDefElem("force_quote", (Node *)collist);
/* TODO: check where the old instance is freed */
}
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next->next;
break;
}
case PREDEF_FMT_OPT_ID_FILLMISSINGFIELDS: {
DefElem *newde =
makeDefElem("fill_missing_fields", (Node *)makeInteger(TRUE));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next->next;
break;
}
case PREDEF_FMT_OPT_ID_NEWLINE: {
DefElem *de = (DefElem *)lfirst(optCell->next);
Value *v = (Value *)(de->arg);
DefElem *newde = makeDefElem("newline", (Node *)makeString(v->val.str));
newOpts = lappend(newOpts, newde);
optCell = optCell->next->next;
break;
}
case PREDEF_FMT_OPT_ID_UNPREDEFINED: {
/*
* In case it is a user defined option. we combind all continuous
* ident until we see a string constant or a integer constant.
* So this means user defined formatter's user defined option
* values can only be string or integer values.
*/
int c = 0;
int identlength = 0;
ListCell *walkerCell = optCell;
while (walkerCell != NULL &&
strcmp("#ident", ((DefElem *)lfirst(walkerCell))->defname) ==
0) {
c++;
Value *v = (Value *)(((DefElem *)lfirst(walkerCell))->arg);
identlength += strlen(v->val.str) + 1;
walkerCell = walkerCell->next;
}
/* Decide the value part */
Node *value = NULL;
if (walkerCell == NULL) {
/* The case the option without value. we set TRUE for it. */
value = makeInteger(TRUE);
} else {
DefElem *de = (DefElem *)lfirst(walkerCell);
if (strcmp("#collist", de->defname) == 0) {
/*
* We don't accept column name list value types for
* customized formatter's user defined options.
*/
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"cannot support column name list as an unknown "
"option's value"),
errOmitLocation(true)));
} else if (strcmp("#int", de->defname) == 0) {
value = makeInteger(((Value *)(de->arg))->val.ival);
} else {
value = makeString(((Value *)(de->arg))->val.str);
}
}
/* Build key part. */
char *newKey = (char *)palloc0(sizeof(char) * identlength);
ListCell *walkerCell2 = optCell;
int counter = 0;
for (; counter < c; counter++, walkerCell2 = walkerCell2->next) {
Value *v = (Value *)(((DefElem *)lfirst(walkerCell2))->arg);
if (counter > 0) {
strcat(newKey, "_");
}
strcat(newKey, v->val.str);
}
DefElem *newde = makeDefElem(newKey, (Node *)value);
newOpts = lappend(newOpts, newde);
if (walkerCell)
optCell = walkerCell->next;
else
optCell = NULL;
break;
}
case PREDEF_FMT_OPT_ID_ILLEGAL: {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot recognize full formatter option list"),
errOmitLocation(true)));
}
}
}
/* Use new list to replace the old one */
createExtStmt->base.options = newOpts;
}
static Query *transformCreateVlabelStmt(ParseState *pstate,
CreateVlabelStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *q;
cxt.stmtType = "CREATE VERTEX";
cxt.relation = stmt->relation;
cxt.hasoids = false;
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.pkey = NULL;
cxt.blist = NIL;
cxt.alist = NIL;
ListCell *elements;
foreach (elements, stmt->tableElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_ColumnDef:
transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
ColumnDef *column = (ColumnDef *)element;
Type type = typenameType(NULL, column->typname);
column->typname->typid = typeTypeId(type);
ReleaseType(type);
break;
case T_Constraint:
transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
break;
case T_FkConstraint:
/* should never happen. If it does fix gram.y */
elog(ERROR, "node type %d not supported for vlabel",
(int)nodeTag(element));
break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
break;
}
}
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ixconstraints;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
static Query *transformCreateElabelStmt(ParseState *pstate,
CreateElabelStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *q;
cxt.stmtType = "CREATE EDGE";
cxt.relation = stmt->relation;
cxt.hasoids = false;
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.pkey = NULL;
cxt.blist = NIL;
cxt.alist = NIL;
ListCell *elements;
foreach (elements, stmt->tableElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_ColumnDef:
transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
ColumnDef *column = (ColumnDef *)element;
Type type = typenameType(NULL, column->typname);
column->typname->typid = typeTypeId(type);
ReleaseType(type);
break;
case T_Constraint:
transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
break;
case T_FkConstraint:
/* should never happen. If it does fix gram.y */
elog(ERROR, "node type %d not supported for vlabel",
(int)nodeTag(element));
break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
break;
}
}
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ixconstraints;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
static Query *transformCreateGraphStmt(ParseState *pstate,
CreateGraphStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *q;
cxt.stmtType = "CREATE GRAPH";
cxt.relation = stmt->graph;
cxt.hasoids = false;
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.pkey = NULL;
cxt.blist = NIL;
cxt.alist = NIL;
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
Query *transformCreateExternalStmtImpl(ParseState *pstate,
CreateExternalStmt *stmt,
List **extras_before,
List **extras_after) {
return transformCreateExternalStmt(pstate, stmt, extras_before, extras_after);
}
static Query *transformCreateExternalStmt(ParseState *pstate,
CreateExternalStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *q;
ListCell *elements;
ExtTableTypeDesc *desc = NULL;
List *likeDistributedBy = NIL;
bool bQuiet = false; /* shut up transformDistributedBy messages */
bool onmaster = false;
bool iswritable = stmt->iswritable;
bool isPluggableStorage = false;
if (!stmt->forceCreateDir) stmt->forceCreateDir = stmt->iswritable;
cxt.stmtType = "CREATE EXTERNAL TABLE";
cxt.isExternalTable = true;
cxt.relation = stmt->base.relation;
cxt.inhRelations = stmt->base.inhRelations;
cxt.isaddpart = stmt->base.is_add_part;
cxt.iswritable = stmt->iswritable;
cxt.exttypedesc = stmt->exttypedesc;
cxt.format = stmt->format;
cxt.parentPath = stmt->parentPath;
cxt.hasoids = false;
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.pkey = NULL;
cxt.blist = NIL;
cxt.alist = NIL;
/*
* Build type description of internal table in pluggable storage
* framework based on format
*/
desc = (ExtTableTypeDesc *)(stmt->exttypedesc);
if (desc->exttabletype == EXTTBL_TYPE_UNKNOWN) {
if (stmt->format == NULL) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Internal table must have format specification"),
errhint("Use CREATE TABLE FORMAT instead"),
errOmitLocation(true)));
}
/* text, csv on hdfs */
if (pg_strncasecmp(stmt->format, "text", strlen("text")) == 0 ||
pg_strncasecmp(stmt->format, "csv", strlen("csv")) == 0) {
desc->exttabletype = EXTTBL_TYPE_LOCATION;
desc->location_list = NIL;
// desc->location_list = list_make1((Node *) makeString(PROTOCOL_HDFS));
desc->command_string = NULL;
desc->on_clause = NIL;
} else {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Format \"%s\" for internal table is invalid",
stmt->format)));
}
isPluggableStorage = true;
}
if (desc->exttabletype == EXTTBL_TYPE_LOCATION) {
/* magma format in magma */
if (pg_strncasecmp(stmt->format, "magma", strlen("magma")) == 0) {
desc->exttabletype = EXTTBL_TYPE_MAGMA;
isPluggableStorage = true;
}
ListCell *loc_cell = list_head(desc->location_list);
if (loc_cell == NIL) {
if (pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
pg_strncasecmp(stmt->format, "text", strlen("text")) &&
pg_strncasecmp(stmt->format, "csv", strlen("csv")) &&
pg_strncasecmp(stmt->format, "magmatp", strlen("magmatp")) &&
pg_strncasecmp(stmt->format, "magmaap", strlen("magmaap"))) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"Internal table on hdfs must be \'orc\', "
"\'text\', \'magmatp\', \'magmaap\' or \'csv\' format")));
}
isPluggableStorage = true;
} else {
Value *loc_val = lfirst(loc_cell);
char *loc_str = pstrdup(loc_val->val.str);
bool is_hive_protocol = IS_HIVE_URI(loc_str);
bool is_magma_protocol = IS_MAGMA_URI(loc_str);
bool is_hdfs_protocol = IS_HDFS_URI(loc_str);
isPluggableStorage = is_hdfs_protocol || is_magma_protocol || is_hive_protocol;
if (is_hive_protocol &&
(pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
pg_strncasecmp(stmt->format, "csv", strlen("csv")) &&
pg_strncasecmp(stmt->format, "text", strlen("text")))) {
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"LOCATION using hive url \'%s\' does not "
"support \'%s\' format",
loc_str, stmt->format),
errhint("Use \"FORMAT \'orc\', \'text\', or \'csv\'\" instead"),
errOmitLocation(true)));
}
if (is_magma_protocol &&
pg_strncasecmp(stmt->format, "magma", strlen("magma"))) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"LOCATION using magma url \'%s\' does not "
"support \'%s\' format",
loc_str, stmt->format),
errhint("Use \"FORMAT \'magma\'\" instead"),
errOmitLocation(true)));
}
if (is_hdfs_protocol &&
(pg_strncasecmp(stmt->format, "orc", strlen("orc")) &&
pg_strncasecmp(stmt->format, "text", strlen("text")) &&
pg_strncasecmp(stmt->format, "csv", strlen("csv")))) {
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"LOCATION using hdfs url \'%s\' does not "
"support \'%s\' format",
loc_str, stmt->format),
errhint("Use \"FORMAT \'orc\', \'text\', or \'csv\'\" instead"),
errOmitLocation(true)));
}
}
}
// handle error table for text/csv pluggable storage
if (stmt->sreh && isPluggableStorage &&
(strcasecmp(stmt->format, "text") == 0 ||
strcasecmp(stmt->format, "csv") == 0)) {
SingleRowErrorDesc *errDesc = (SingleRowErrorDesc *)stmt->sreh;
if (!errDesc->is_limit_in_rows) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"Single row error handling with percentage limit is "
"not accepted for pluggable storage")));
}
errDesc->is_hdfs_protocol_text = true;
if (errDesc->errtable) {
errDesc->hdfsLoc = (char *)palloc0(MAXPGPATH);
char *fileSpacePath = NULL;
GetFilespacePathForTablespace(get_database_dts(MyDatabaseId),
&fileSpacePath);
// in magma case, database has no default filespace path, use dfs_url
// instead for error table.
fileSpacePath = fileSpacePath == NULL ? getDefaultFilespace()
: fileSpacePath;
uuid_t uuid;
char buf[1024];
uuid_generate(uuid);
uuid_unparse(uuid, buf);
sprintf(errDesc->hdfsLoc, "%s/ExtErrTbl/%s", fileSpacePath, buf);
if (fileSpacePath)
{
pfree(fileSpacePath);
}
}
}
// Only on top-most partitioned tables
if (stmt->base.partitionBy && !stmt->base.is_part_child) {
if (isPluggableStorage)
fixCreateStmtForPartitionedTable(&stmt->base);
else
elog(ERROR,
"Partition external table only supported for pluggable storage");
}
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis.
*/
foreach (elements, stmt->base.tableElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_ColumnDef:
transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
break;
case T_Constraint:
transformExtTableConstraint(pstate, &cxt, (Constraint *)element);
break;
case T_FkConstraint:
/* should never happen. If it does fix gram.y */
elog(ERROR, "node type %d not supported for external tables",
(int)nodeTag(element));
break;
case T_InhRelation: {
/* LIKE */
bool isBeginning = (cxt.columns == NIL);
transformInhRelation(pstate, &cxt, (InhRelation *)element,
!isPluggableStorage);
if (Gp_role == GP_ROLE_DISPATCH && isBeginning &&
stmt->base.distributedBy == NIL && stmt->policy == NULL &&
iswritable /* dont bother if readable table */) {
likeDistributedBy = getLikeDistributionPolicy((InhRelation *)element);
}
} break;
case T_String:
break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
break;
}
}
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
/*
* Postprocess constraints that give rise to index definitions.
*/
transformIndexConstraints(pstate, &cxt, false);
/*
* Check if this is an EXECUTE ON MASTER table. We'll need this information
* in transformExternalDistributedBy. While at it, we also check if an error
* table is attempted to be used on ON MASTER table and error if so.
*/
if (!iswritable) {
desc = (ExtTableTypeDesc *)stmt->exttypedesc;
if (desc->exttabletype == EXTTBL_TYPE_EXECUTE) {
ListCell *exec_location_opt;
foreach (exec_location_opt, desc->on_clause) {
DefElem *defel = (DefElem *)lfirst(exec_location_opt);
if (strcmp(defel->defname, "master") == 0) {
SingleRowErrorDesc *srehDesc = (SingleRowErrorDesc *)stmt->sreh;
onmaster = true;
if (srehDesc && srehDesc->errtable)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"External web table with ON MASTER clause "
"cannot use error tables.")));
}
}
}
}
/*
* Check if we need to create an error table. If so, add it to the
* before list.
*/
if (stmt->sreh && ((SingleRowErrorDesc *)stmt->sreh)->errtable)
transformSingleRowErrorHandling(pstate, &cxt,
(SingleRowErrorDesc *)stmt->sreh);
transformETDistributedBy(pstate, &cxt, &(stmt->base.distributedBy),
&stmt->policy, NULL, /*no WITH options for ET*/
likeDistributedBy, bQuiet, iswritable, onmaster);
// Process table partitioning clause
if (isPluggableStorage)
transformPartitionBy(pstate, &cxt, &stmt->base, stmt->base.partitionBy,
stmt->policy);
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
stmt->base.tableElts = cxt.columns;
stmt->base.constraints = cxt.ckconstraints;
stmt->pkey = cxt.pkey;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
static Query *transformCreateForeignStmt(ParseState *pstate,
CreateForeignStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *q;
ListCell *elements;
cxt.stmtType = "CREATE FOREIGN TABLE";
cxt.isExternalTable = false;
cxt.relation = stmt->relation;
cxt.inhRelations = NIL;
cxt.hasoids = false;
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.pkey = NULL;
cxt.blist = NIL;
cxt.alist = NIL;
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis.
*/
foreach (elements, stmt->tableElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_ColumnDef:
transformColumnDefinition(pstate, &cxt, (ColumnDef *)element);
break;
case T_Constraint:
case T_FkConstraint:
/* should never happen. If it does fix gram.y */
elog(ERROR, "node type %d not supported for foreign tables",
(int)nodeTag(element));
break;
case T_InhRelation: {
/* LIKE */
transformInhRelation(pstate, &cxt, (InhRelation *)element, true);
} break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
break;
}
}
Assert(cxt.ckconstraints == NIL);
Assert(cxt.fkconstraints == NIL);
Assert(cxt.ixconstraints == NIL);
/*
* Output results.
*/
q = makeNode(Query);
q->commandType = CMD_UTILITY;
q->utilityStmt = (Node *)stmt;
stmt->tableElts = cxt.columns;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return q;
}
static void transformColumnDefinition(ParseState *pstate,
CreateStmtContext *cxt,
ColumnDef *column) {
bool is_serial;
bool saw_nullable;
bool saw_default;
Constraint *constraint;
ListCell *clist;
cxt->columns = lappend(cxt->columns, column);
/* Check for SERIAL pseudo-types */
is_serial = false;
if (list_length(column->typname->names) == 1) {
char *typname = strVal(linitial(column->typname->names));
if (strcmp(typname, "serial") == 0 || strcmp(typname, "serial4") == 0) {
is_serial = true;
column->typname->names = NIL;
column->typname->typid = INT4OID;
} else if (strcmp(typname, "bigserial") == 0 ||
strcmp(typname, "serial8") == 0) {
is_serial = true;
column->typname->names = NIL;
column->typname->typid = INT8OID;
}
}
/* Do necessary work on the column type declaration */
transformColumnType(pstate, column);
/* Special actions for SERIAL pseudo-types */
if (is_serial) {
Oid snamespaceid;
char *snamespace;
char *sname;
char *qstring;
A_Const *snamenode;
FuncCall *funccallnode;
CreateSeqStmt *seqstmt;
AlterSeqStmt *altseqstmt;
List *attnamelist;
/*
* Determine namespace and name to use for the sequence.
*
* Although we use ChooseRelationName, it's not guaranteed that the
* selected sequence name won't conflict; given sufficiently long
* field names, two different serial columns in the same table could
* be assigned the same sequence name, and we'd not notice since we
* aren't creating the sequence quite yet. In practice this seems
* quite unlikely to be a problem, especially since few people would
* need two serial columns in one table.
*/
snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
snamespace = get_namespace_name(snamespaceid);
sname = ChooseRelationName(cxt->relation->relname, column->colname, "seq",
snamespaceid, NULL);
ereport(NOTICE, (errmsg(
"%s will create implicit sequence \"%s\" for serial "
"column \"%s.%s\"",
cxt->stmtType, sname, cxt->relation->relname,
column->colname)));
/*
* Build a CREATE SEQUENCE command to create the sequence object, and
* add it to the list of things to be done before this CREATE/ALTER
* TABLE.
*/
seqstmt = makeNode(CreateSeqStmt);
seqstmt->sequence =
makeRangeVar(NULL /*catalogname*/, snamespace, sname, -1);
seqstmt->sequence->istemp = cxt->relation->istemp;
seqstmt->options = NIL;
cxt->blist = lappend(cxt->blist, seqstmt);
/*
* Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence
* as owned by this column, and add it to the list of things to be
* done after this CREATE/ALTER TABLE.
*/
altseqstmt = makeNode(AlterSeqStmt);
altseqstmt->sequence =
makeRangeVar(NULL /*catalogname*/, snamespace, sname, -1);
attnamelist =
list_make3(makeString(snamespace), makeString(cxt->relation->relname),
makeString(column->colname));
altseqstmt->options =
list_make1(makeDefElem("owned_by", (Node *)attnamelist));
cxt->alist = lappend(cxt->alist, altseqstmt);
/*
* Create appropriate constraints for SERIAL. We do this in full,
* rather than shortcutting, so that we will detect any conflicting
* constraints the user wrote (like a different DEFAULT).
*
* Create an expression tree representing the function call
* nextval('sequencename'). We cannot reduce the raw tree to cooked
* form until after the sequence is created, but there's no need to do
* so.
*/
qstring = quote_qualified_identifier(snamespace, sname);
snamenode = makeNode(A_Const);
snamenode->val.type = T_String;
snamenode->val.val.str = qstring;
snamenode->typname = SystemTypeName("regclass");
snamenode->location = -1; /*CDB*/
funccallnode = makeNode(FuncCall);
funccallnode->funcname = SystemFuncName("nextval");
funccallnode->args = list_make1(snamenode);
funccallnode->agg_star = false;
funccallnode->agg_distinct = false;
funccallnode->func_variadic = false;
funccallnode->location = -1;
constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->raw_expr = (Node *)funccallnode;
constraint->cooked_expr = NULL;
constraint->keys = NIL;
column->constraints = lappend(column->constraints, constraint);
constraint = makeNode(Constraint);
constraint->contype = CONSTR_NOTNULL;
column->constraints = lappend(column->constraints, constraint);
}
/* Process column constraints, if any... */
transformConstraintAttrs(column->constraints);
saw_nullable = false;
saw_default = false;
foreach (clist, column->constraints) {
constraint = lfirst(clist);
/*
* If this column constraint is a FOREIGN KEY constraint, then we fill
* in the current attribute's name and throw it into the list of FK
* constraints to be processed later.
*/
if (IsA(constraint, FkConstraint)) {
FkConstraint *fkconstraint = (FkConstraint *)constraint;
fkconstraint->fk_attrs = list_make1(makeString(column->colname));
cxt->fkconstraints = lappend(cxt->fkconstraints, fkconstraint);
continue;
}
Assert(IsA(constraint, Constraint));
switch (constraint->contype) {
case CONSTR_NULL:
if (saw_nullable && column->is_not_null)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"conflicting NULL/NOT NULL declarations for "
"column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname)));
column->is_not_null = FALSE;
saw_nullable = true;
break;
case CONSTR_NOTNULL:
if (saw_nullable && !column->is_not_null)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"conflicting NULL/NOT NULL declarations for "
"column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname)));
column->is_not_null = TRUE;
saw_nullable = true;
break;
case CONSTR_DEFAULT:
if (saw_default)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"multiple default values specified for column "
"\"%s\" of table \"%s\"",
column->colname, cxt->relation->relname)));
/*
* Note: DEFAULT NULL maps to constraint->raw_expr == NULL
*
* We lose the knowledge that the user specified DEFAULT NULL at
* this point, so we record it in default_is_null
*/
column->raw_default = constraint->raw_expr;
column->default_is_null = !constraint->raw_expr;
Assert(constraint->cooked_expr == NULL);
saw_default = true;
break;
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
case CONSTR_ATTR_DEFERRABLE:
case CONSTR_ATTR_NOT_DEFERRABLE:
case CONSTR_ATTR_DEFERRED:
case CONSTR_ATTR_IMMEDIATE:
/* transformConstraintAttrs took care of these */
break;
default:
elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
break;
}
}
}
static void transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
Constraint *constraint) {
switch (constraint->contype) {
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
case CONSTR_NULL:
case CONSTR_NOTNULL:
case CONSTR_DEFAULT:
case CONSTR_ATTR_DEFERRABLE:
case CONSTR_ATTR_NOT_DEFERRABLE:
case CONSTR_ATTR_DEFERRED:
case CONSTR_ATTR_IMMEDIATE:
elog(ERROR, "invalid context for constraint type %d",
constraint->contype);
break;
default:
elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
break;
}
}
static void transformExtTableConstraint(ParseState *pstate,
CreateStmtContext *cxt,
Constraint *constraint) {
switch (constraint->contype) {
case CONSTR_PRIMARY:
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
case CONSTR_CHECK:
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
default:
elog(ERROR, "unrecognized constraint type: %d", constraint->contype);
break;
}
}
/*
* transformETDistributedBy - transform DISTRIBUTED BY clause for
* external tables.
*
* WET: by default we distribute RANDOMLY, or by the distribution key
* of the LIKE table if exists. However, if DISTRIBUTED BY was
* specified we use it by calling the regular transformDistributedBy and
* handle it like we would for non external tables.
*
* RET: We always create a random distribution policy entry *unless*
* this is an EXECUTE table with ON MASTER specified, in which case
* we create no policy so that the master will be accessed.
*/
static void transformETDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
List **distributedBy, GpPolicy **policyp,
List *options, List *likeDistributedBy,
bool bQuiet, bool iswritable,
bool onmaster) {
int maxattrs = 200;
GpPolicy *p = NULL;
/*
* utility mode creates can't have a policy. Only the QD can have policies
*/
if (Gp_role != GP_ROLE_DISPATCH) {
*policyp = NULL;
return;
}
if (!iswritable && list_length(*distributedBy) > 0)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"Readable external tables can\'t specify a DISTRIBUTED "
"BY clause.")));
if (iswritable) {
/* WET */
if ((pg_strcasecmp(cxt->format, "magmaap") == 0
|| pg_strcasecmp(cxt->format, "magmatp") == 0)
&& cxt->columns) // support magma table with no column
{/* defaults to use first column as distribution key with magma table*/
p = (GpPolicy *) palloc(
sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
p->ptype = POLICYTYPE_PARTITIONED;
// distributed randomly give a NULL node to the list
// no distributed by clause or distributed randomly both use first column
// as distributed key for magma table
if (*distributedBy != NIL
&& lfirst(list_head(*distributedBy)) == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg( "magmatp or "
"magmaap table do not support \"distributed randomly\" yet.")));
}
if (*distributedBy == NIL)
{
ColumnDef *first_column = (ColumnDef *) linitial(cxt->columns);
char *attname = pstrdup(first_column->colname);
*distributedBy = lappend(*distributedBy,
(Node *) makeString(attname));
assert(list_length(*distributedBy) == 1);
p->nattrs = 1;
p->attrs[0] = 1;
}
/* regular DISTRIBUTED BY transformation */
transformDistributedBy(pstate, cxt, *distributedBy, &p, options,
likeDistributedBy, bQuiet);
p->bucketnum = GetDefaultMagmaBucketNum();
*policyp = p;
return;
}
if (*distributedBy == NIL && likeDistributedBy == NIL) {
/* defaults to DISTRIBUTED RANDOMLY */
p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
p->ptype = POLICYTYPE_PARTITIONED;
p->nattrs = 0;
p->bucketnum = GetRelOpt_bucket_num_fromOptions(
options, GetExternalTablePartitionNum());
p->attrs[0] = 1;
*policyp = p;
} else {
/* regular DISTRIBUTED BY transformation */
transformDistributedBy(pstate, cxt, *distributedBy, policyp, options,
likeDistributedBy, bQuiet);
}
} else {
/* RET */
if (onmaster) {
p = NULL;
} else {
/* defaults to DISTRIBUTED RANDOMLY */
p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
p->ptype = POLICYTYPE_PARTITIONED;
p->nattrs = 0;
p->bucketnum = GetRelOpt_bucket_num_fromOptions(
options, GetExternalTablePartitionNum());
p->attrs[0] = 1;
}
*policyp = p;
}
}
/****************stmt->policy*********************/
static void transformDistributedBy(ParseState *pstate, CreateStmtContext *cxt,
List *distributedBy, GpPolicy **policyp,
List *options, List *likeDistributedBy,
bool bQuiet) {
ListCell *keys = NULL;
GpPolicy *policy = NULL;
int colindex = 0;
int maxattrs = 200;
/*
* utility mode creates can't have a policy. Only the QD can have policies
*
*/
if (Gp_role != GP_ROLE_DISPATCH) {
*policyp = NULL;
return;
}
/*
* Currently heap table only exists in hawq's master, so there is no
* policy information.
*/
if (enable_heap_table_on_master) {
bool appendonly;
bool hasAppendOnly = GetRelOpt_appendonly_fromOptions(options, &appendonly);
if (hasAppendOnly && !appendonly) {
*policyp = NULL;
return;
}
}
policy = (GpPolicy *)palloc(sizeof(GpPolicy) +
maxattrs * sizeof(policy->attrs[0]));
policy->ptype = POLICYTYPE_PARTITIONED;
policy->nattrs = 0;
policy->bucketnum = -1;
policy->attrs[0] = 1;
/*
* If new table INHERITS from one or more parent tables, check parents.
*/
if (cxt->inhRelations != NIL) {
ListCell *entry;
bool isMagmaTable = false;
if (cxt->exttypedesc && cxt->exttypedesc->type == EXTTBL_TYPE_MAGMA) {
isMagmaTable = true;
}
foreach (entry, cxt->inhRelations) {
RangeVar *parent = (RangeVar *)lfirst(entry);
Oid relId = RangeVarGetRelid(parent, false, false /*allowHcatalog*/);
bool isOldTableMagma = dataStoredInMagmaByOid(relId);
GpPolicy *oldTablePolicy = GpPolicyFetch(CurrentMemoryContext, relId);
/* Partitioned child must have partitioned parents. */
if (oldTablePolicy == NULL ||
oldTablePolicy->ptype != POLICYTYPE_PARTITIONED) {
ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg(
"cannot inherit from catalog table \"%s\" "
"to create table \"%s\".",
parent->relname, cxt->relation->relname),
errdetail(
"An inheritance hierarchy cannot contain a "
"mixture of distributed and "
"non-distributed tables.")));
}
/*
* If we still don't know what distribution to use, and this
* is an inherited table, set the distribution based on the
* parent (or one of the parents)
*/
if (distributedBy == NIL && oldTablePolicy->nattrs >= 0) {
int ia;
if (oldTablePolicy->nattrs > 0) {
for (ia = 0; ia < oldTablePolicy->nattrs; ia++) {
char *attname = get_attname(relId, oldTablePolicy->attrs[ia]);
distributedBy = lappend(distributedBy, (Node *)makeString(attname));
}
} else {
/* strewn parent */
distributedBy = lappend(distributedBy, (Node *)NULL);
}
if (options != NIL)
policy->bucketnum = GetRelOpt_bucket_num_fromOptions(options, -1);
/*
* If child table is not magma table and parent table is magma table, do not
* inherit the bucketnum of parent table, use value of default_hash_table_bucket_number
* instead.
*/
if (policy->bucketnum == -1)
{
if (isOldTableMagma && !isMagmaTable)
{
policy->bucketnum = default_hash_table_bucket_number;
}
else
{
policy->bucketnum = oldTablePolicy->bucketnum;
}
}
if (!bQuiet)
elog(NOTICE,
"Table has parent, setting distribution columns "
"to match parent table");
}
pfree(oldTablePolicy);
}
}
if (distributedBy == NIL && likeDistributedBy != NIL) {
distributedBy = likeDistributedBy;
if (!bQuiet)
elog(NOTICE,
"Table doesn't have 'distributed by' clause, "
"defaulting to distribution columns from LIKE table");
}
if (distributedBy == NIL) {
/*
* if we get here, we haven't a clue what to use for the distribution
* columns.
* Distributed by randomly.
*/
policy->nattrs = 0;
policy->bucketnum = -1;
} else {
/*
* We have a DISTRIBUTED BY column list, either specified by the user
* or defaulted to like table or inherit parent table . Process it now and
* set the distribution policy.
*/
policy->nattrs = 0;
if (!(distributedBy->length == 1 && linitial(distributedBy) == NULL)) {
foreach (keys, distributedBy) {
char *key = strVal(lfirst(keys));
bool found = false;
ColumnDef *column = NULL;
ListCell *columns;
colindex = 0;
if (cxt->inhRelations) {
/* try inherited tables */
ListCell *inher;
foreach (inher, cxt->inhRelations) {
RangeVar *inh = (RangeVar *)lfirst(inher);
Relation rel;
int count;
Assert(IsA(inh, RangeVar));
rel = heap_openrv(inh, AccessShareLock);
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table",
inh->relname)));
for (count = 0; count < rel->rd_att->natts; count++) {
Form_pg_attribute inhattr = rel->rd_att->attrs[count];
char *inhname = NameStr(inhattr->attname);
if (inhattr->attisdropped) continue;
colindex++;
if (strcmp(key, inhname) == 0) {
found = true;
break;
}
}
heap_close(rel, NoLock);
if (found) {
elog(
DEBUG1,
"DISTRIBUTED BY clause refers to columns of inherited table");
break;
}
}
}
if (!found) {
foreach (columns, cxt->columns) {
column = (ColumnDef *)lfirst(columns);
Assert(IsA(column, ColumnDef));
colindex++;
if (strcmp(column->colname, key) == 0) {
Oid typeOid = typenameTypeId(NULL, column->typname);
/*
* To be a part of a distribution key, this type must
* be supported for hashing internally in Greenplum
* Database. We check if the base type is supported
* for hashing or if it is an array type (we support
* hashing on all array types).
*/
if (!isGreenplumDbHashable(typeOid)) {
ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg(
"type \"%s\" can't be a part of a "
"distribution key",
format_type_be(typeOid))));
}
found = true;
break;
}
}
}
/*
* In the ALTER TABLE case, don't complain about index keys
* not created in the command; they may well exist already.
* DefineIndex will complain about them if not, and will also
* take care of marking them NOT NULL.
*/
if (!found && !cxt->isalter)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg(
"column \"%s\" named in 'DISTRIBUTED BY' clause "
"does not exist",
key),
errOmitLocation(true)));
policy->attrs[policy->nattrs++] = colindex;
}
/* MPP-14770: we should check for duplicate column usage */
foreach (keys, distributedBy) {
char *key = strVal(lfirst(keys));
ListCell *lkeys = NULL;
for_each_cell(lkeys, keys->next) {
char *lkey = strVal(lfirst(lkeys));
if (strcmp(key, lkey) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("duplicate column \"%s\" in DISTRIBUTED BY clause",
key)));
}
}
if ((policy->nattrs > 0) && (policy->bucketnum == -1)) {
policy->bucketnum = GetRelOpt_bucket_num_fromOptions(
options, GetHashDistPartitionNum());
}
}
}
if (policy->bucketnum == -1) {
policy->bucketnum =
GetRelOpt_bucket_num_fromOptions(options, GetDefaultPartitionNum());
}
*policyp = policy;
if (cxt && cxt->inhRelations) {
ListCell *entry;
foreach (entry, cxt->inhRelations) {
RangeVar *parent = (RangeVar *)lfirst(entry);
Oid relId = RangeVarGetRelid(parent, false, false /*allowHcatalog*/);
GpPolicy *parentPolicy = GpPolicyFetch(CurrentMemoryContext, relId);
if (!GpPolicyEqual(policy, parentPolicy)) {
ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg(
"distribution policy for \"%s\" "
"must be the same as that for \"%s\"",
cxt->relation->relname, parent->relname)));
}
}
}
if (cxt && cxt->pkey) /* Primary key specified. Make sure
* distribution columns match */
{
int i = 0;
IndexStmt *index = cxt->pkey;
List *indexParams = index->indexParams;
ListCell *ip;
if(list_length(indexParams) < policy->nattrs)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"PRIMARY KEY and DISTRIBUTED BY definitions incompatible"),
errhint("DISTRIBUTED KEY columns must be equal to or a "
"left-subset of PRIMARY KEY columns, the default "
"DISTRIBUTED KEY is first column."),
errOmitLocation(true)));
}
foreach (ip, indexParams) {
IndexElem *iparam;
if (i >= policy->nattrs) break;
iparam = lfirst(ip);
if (iparam->name != 0) {
bool found = false;
ColumnDef *column = NULL;
ListCell *columns;
colindex = 0;
if (cxt->inhRelations) {
/* try inherited tables */
ListCell *inher;
foreach (inher, cxt->inhRelations) {
RangeVar *inh = (RangeVar *)lfirst(inher);
Relation rel;
int count;
Assert(IsA(inh, RangeVar));
rel = heap_openrv(inh, AccessShareLock);
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table",
inh->relname)));
for (count = 0; count < rel->rd_att->natts; count++) {
Form_pg_attribute inhattr = rel->rd_att->attrs[count];
char *inhname = NameStr(inhattr->attname);
if (inhattr->attisdropped) continue;
colindex++;
if (strcmp(iparam->name, inhname) == 0) {
found = true;
break;
}
}
heap_close(rel, NoLock);
if (found)
elog(DEBUG1,
"'distributed by' clause refers to "
"columns of inherited table");
if (found) break;
}
}
if (!found) {
foreach (columns, cxt->columns) {
column = (ColumnDef *)lfirst(columns);
Assert(IsA(column, ColumnDef));
colindex++;
if (strcmp(column->colname, iparam->name) == 0) {
found = true;
break;
}
}
}
if (colindex != policy->attrs[i]) {
ereport(
ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"PRIMARY KEY and DISTRIBUTED BY definitions incompatible"),
errhint("DISTRIBUTED KEY columns must be equal to or a "
"left-subset of PRIMARY KEY columns, the default "
"DISTRIBUTED KEY is first column."),
errOmitLocation(true)));
}
i++;
}
}
}
}
/*
* Add any missing encoding attributes (compresstype = none, blocksize=...).
*/
static List *fillin_encoding(List *list) {
bool foundCompressType = false;
bool foundCompressTypeNone = false;
bool snappyCompressType = false;
char *cmplevel = NULL;
bool need_free_cmplevel = false;
bool foundBlockSize = false;
char *arg;
bool columnTable = false;
DefElem *e1 = makeDefElem("compresstype", (Node *)makeString("none"));
DefElem *e2 = makeDefElem("compresslevel",
(Node *)makeInteger(0)); /* compress level 0 */
DefElem *e2b = makeDefElem("compresslevel",
(Node *)makeInteger(1)); /* compress level 1 */
DefElem *e3 = makeDefElem("blocksize",
(Node *)makeInteger(DEFAULT_APPENDONLY_BLOCK_SIZE));
DefElem *zlibComp = makeDefElem("compresstype", (Node *)makeString("zlib"));
DefElem *gzipComp = makeDefElem("compresstype", (Node *)makeString("gzip"));
List *retList = list;
ListCell *lc;
foreach (lc, list) {
DefElem *el = lfirst(lc);
if (pg_strcasecmp("compresstype", el->defname) == 0) {
foundCompressType = true;
bool need_free_arg = false;
arg = defGetString(el, &need_free_arg);
if (pg_strcasecmp("none", arg) == 0) foundCompressTypeNone = true;
if (pg_strcasecmp("snappy", arg) == 0) snappyCompressType = true;
if (need_free_arg) {
pfree(arg);
arg = NULL;
}
AssertImply(need_free_arg, NULL == arg);
} else if (pg_strcasecmp("compresslevel", el->defname) == 0) {
cmplevel = defGetString(el, &need_free_cmplevel);
} else if (pg_strcasecmp("blocksize", el->defname) == 0) {
foundBlockSize = true;
} else if (pg_strcasecmp("orientation", el->defname) == 0) {
bool need_free_arg = false;
arg = defGetString(el, &need_free_arg);
if (pg_strcasecmp("parquet", arg) == 0
|| pg_strcasecmp("orc", arg) == 0)
columnTable = true;
if (need_free_arg) {
pfree(arg);
}
}
}
if (foundCompressType == false) {
/*
* We actually support "compresslevel=N" and default the compression
* type to zlib (see default_reloptions()). There's no pleasant way to
* factor out the common code. We only add zlib compression for non-zero
* N in compresslevel=N (we check if N is meaningful to zlib later).
*/
if (cmplevel && strcmp(cmplevel, "0") != 0) {
if (!columnTable)
retList = lappend(retList, zlibComp);
else
retList = lappend(retList, gzipComp);
} else
retList = lappend(retList, e1);
}
if (!cmplevel) {
if (foundCompressType == false || foundCompressTypeNone == true)
retList = lappend(retList, e2); /* no compress type or snappy compress
type => compresslevel = 0 */
else if (snappyCompressType == false)
retList = lappend(
retList,
e2b); /* compress type, but no compress level => compress level = 1 */
}
if ((foundBlockSize == false) && (columnTable == false))
retList = lappend(retList, e3);
return retList;
}
static int deparse_partition_rule(Node *pNode, char *outbuf, size_t outsize) {
if (!pNode) return 0;
switch (nodeTag(pNode)) {
case T_NullTest: {
NullTest *nt = (NullTest *)pNode;
char leftbuf[1000];
if (!deparse_partition_rule((Node *)nt->arg, leftbuf, sizeof(leftbuf)))
return 0;
snprintf(outbuf, outsize, "%s %s", leftbuf,
nt->nulltesttype == IS_NULL ? "ISNULL" : "IS NOT NULL");
}
break;
case T_Value:
/* XXX XXX XXX ??? */
break;
case T_ColumnRef: {
ColumnRef *pCRef = (ColumnRef *)pNode;
List *coldefs = pCRef->fields;
ListCell *lc = NULL;
StringInfoData sid;
int colcnt = 0;
initStringInfo(&sid);
lc = list_head(coldefs);
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
char leftBuf[10000];
if (!deparse_partition_rule(pCol, leftBuf, sizeof(leftBuf))) return 0;
if (colcnt) {
appendStringInfo(&sid, ".");
}
appendStringInfo(&sid, "%s", leftBuf);
colcnt++;
} /* end for all cols */
outbuf[0] = '\0';
snprintf(outbuf, outsize, "%s", sid.data);
pfree(sid.data);
break;
}
case T_String:
snprintf(outbuf, outsize, "%s", strVal(pNode));
break;
case T_Integer:
snprintf(outbuf, outsize, "%ld", intVal(pNode));
break;
case T_Float:
snprintf(outbuf, outsize, "%f", floatVal(pNode));
break;
case T_A_Const: {
A_Const *acs = (A_Const *)pNode;
if (acs->val.type == T_String) {
if (acs->typname) /* deal with explicit types */
{
/* XXX XXX: simple types only -- need to
* handle Interval, etc */
snprintf(outbuf, outsize, "\'%s\'::%s", acs->val.val.str,
TypeNameToString(acs->typname));
} else {
snprintf(outbuf, outsize, "\'%s\'", acs->val.val.str);
}
return 1;
}
return (deparse_partition_rule((Node *)&(acs->val), outbuf, outsize));
} break;
case T_A_Expr: {
A_Expr *ax = (A_Expr *)pNode;
char leftBuf[10000];
char rightBuf[10000];
char *infix_op;
switch (ax->kind) {
case AEXPR_OP: /* normal operator */
case AEXPR_AND: /* booleans - name field is unused */
case AEXPR_OR:
break;
default:
return 0;
}
if (!deparse_partition_rule(ax->lexpr, leftBuf, sizeof(leftBuf)))
return 0;
if (!deparse_partition_rule(ax->rexpr, rightBuf, sizeof(rightBuf)))
return 0;
switch (ax->kind) {
case AEXPR_OP: /* normal operator */
infix_op = strVal(lfirst(list_head(ax->name)));
break;
case AEXPR_AND: /* booleans - name field is unused */
infix_op = "AND";
break;
case AEXPR_OR:
infix_op = "OR";
break;
default:
return 0;
}
snprintf(outbuf, outsize, "(%s %s %s)", leftBuf, infix_op, rightBuf);
} break;
case T_Var: {
/* Var *var = (Var *)
* node; */
}
default:
break;
}
return 1;
}
#if 0
static A_Const *
make_a_const(A_Const *pValConst, char *val_str)
{
A_Const *pNewValConst = makeNode(A_Const);
int estat = 0;
Assert(val_str);
pNewValConst->val.type = pValConst->val.type;
pNewValConst->typname = pValConst->typname;
pNewValConst->location = pValConst->location;
switch (pValConst->val.type)
{
case T_Integer:
estat = sscanf(val_str, "%ld",
&(pNewValConst->val.val.ival));
break;
case T_Float:
/* estat = scanf(val_str, "%f",
(pNewValConst->val.val.str)); */
case T_String:
pNewValConst->val.val.str = val_str;
estat = 1;
break;
default:
break;
}
if (estat < 1)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("unknown type or operator: %s",
val_str)));
}
return (pNewValConst);
} /* end make_a_const */
#endif
static Node *make_prule_catalog(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
PartitionElem *pElem, char *at_depth,
char *child_name_str, char *exprBuf,
Node *pWhere) {
Node *pResult = NULL;
InsertStmt *pIns = NULL;
RangeVar *parent_tab_name;
RangeVar *child_tab_name;
char ruleBuf[10000];
char newVals[10000];
{
List *coldefs = stmt->tableElts;
ListCell *lc = NULL;
StringInfoData sid;
int colcnt = 0;
initStringInfo(&sid);
lc = list_head(coldefs);
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
ColumnDef *pColDef;
if (nodeTag(pCol) != T_ColumnDef) /* avoid constraints, etc */
continue;
pColDef = (ColumnDef *)pCol;
if (colcnt) {
appendStringInfo(&sid, ", ");
}
appendStringInfo(&sid, "new.%s", pColDef->colname);
colcnt++;
} /* end for all cols */
newVals[0] = '\0';
snprintf(newVals, sizeof(newVals), "VALUES (%s)", sid.data);
pfree(sid.data);
}
parent_tab_name = makeNode(RangeVar);
parent_tab_name->catalogname = cxt->relation->catalogname;
parent_tab_name->schemaname = cxt->relation->schemaname;
parent_tab_name->relname = cxt->relation->relname;
parent_tab_name->location = -1;
child_tab_name = makeNode(RangeVar);
child_tab_name->catalogname = cxt->relation->catalogname;
child_tab_name->schemaname = cxt->relation->schemaname;
child_tab_name->relname = child_name_str;
child_tab_name->location = -1;
snprintf(
ruleBuf, sizeof(ruleBuf),
"CREATE RULE %s AS ON INSERT to %s WHERE %s DO INSTEAD INSERT INTO %s %s",
child_name_str, parent_tab_name->relname, exprBuf, child_name_str,
newVals);
pIns = makeNode(InsertStmt);
pResult = (Node *)pIns;
pIns->relation = makeNode(RangeVar);
pIns->relation->catalogname = NULL;
pIns->relation->schemaname = NULL;
pIns->relation->relname = "partition_rule";
pIns->relation->location = -1;
pIns->returningList = NULL;
pIns->cols = NIL;
if (1) {
List *vl1 = NULL;
A_Const *acs = makeNode(A_Const);
acs->val.type = T_String;
acs->val.val.str = pstrdup(parent_tab_name->relname);
acs->typname = SystemTypeName("text");
acs->location = -1;
vl1 = list_make1(acs);
acs = makeNode(A_Const);
acs->val.type = T_String;
acs->val.val.str = pstrdup(child_name_str);
acs->typname = SystemTypeName("text");
acs->location = -1;
vl1 = lappend(vl1, acs);
acs = makeNode(A_Const);
acs->val.type = T_String;
acs->val.val.str = pstrdup(exprBuf);
acs->typname = SystemTypeName("text");
acs->location = -1;
vl1 = lappend(vl1, acs);
acs = makeNode(A_Const);
acs->val.type = T_String;
acs->val.val.str = pstrdup(ruleBuf);
acs->typname = SystemTypeName("text");
acs->location = -1;
vl1 = lappend(vl1, acs);
pIns->selectStmt = (Node *)makeNode(SelectStmt);
((SelectStmt *)pIns->selectStmt)->valuesLists = list_make1(vl1);
}
return (pResult);
} /* end make_prule_catalog */
static Node *make_prule_rulestmt(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
PartitionElem *pElem, char *at_depth,
char *child_name_str, char *exprBuf,
Node *pWhere) {
Node *pResult = NULL;
RuleStmt *pRule = NULL;
InsertStmt *pIns = NULL;
RangeVar *parent_tab_name;
RangeVar *child_tab_name;
parent_tab_name = makeNode(RangeVar);
parent_tab_name->catalogname = cxt->relation->catalogname;
parent_tab_name->schemaname = cxt->relation->schemaname;
parent_tab_name->relname = cxt->relation->relname;
parent_tab_name->location = -1;
child_tab_name = makeNode(RangeVar);
child_tab_name->catalogname = cxt->relation->catalogname;
child_tab_name->schemaname = cxt->relation->schemaname;
child_tab_name->relname = child_name_str;
child_tab_name->location = -1;
pIns = makeNode(InsertStmt);
pRule = makeNode(RuleStmt);
pRule->replace = false; /* do not replace */
pRule->relation = parent_tab_name;
pRule->rulename = pstrdup(child_name_str);
pRule->whereClause = pWhere;
pRule->event = CMD_INSERT;
pRule->instead = true; /* do instead */
pRule->actions = list_make1(pIns);
pResult = (Node *)pRule;
pIns->relation = makeNode(RangeVar);
pIns->relation->catalogname = cxt->relation->catalogname;
pIns->relation->schemaname = cxt->relation->schemaname;
pIns->relation->relname = child_name_str;
pIns->relation->location = -1;
pIns->returningList = NULL;
pIns->cols = NIL;
if (1) {
List *coldefs = stmt->tableElts;
ListCell *lc = NULL;
List *vl1 = NULL;
lc = list_head(coldefs);
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
ColumnDef *pColDef;
ColumnRef *pCRef;
if (nodeTag(pCol) != T_ColumnDef) /* avoid constraints, etc */
continue;
pCRef = makeNode(ColumnRef);
pColDef = (ColumnDef *)pCol;
pCRef->location = -1;
/* NOTE: gram.y uses "*NEW*" for "new" */
pCRef->fields =
list_make2(makeString("*NEW*"), makeString(pColDef->colname));
vl1 = lappend(vl1, pCRef);
}
pIns->selectStmt = (Node *)makeNode(SelectStmt);
((SelectStmt *)pIns->selectStmt)->valuesLists = list_make1(vl1);
}
return (pResult);
} /* end make_prule_rulestmt */
List *make_partition_rules(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
PartitionElem *pElem, char *at_depth,
char *child_name_str, int partNumId, int maxPartNum,
int everyOffset, int maxEveryOffset,
ListCell **pp_lc_anp, bool doRuleStmt) {
PartitionBy *pBy = (PartitionBy *)partitionBy;
Node *pRule = NULL;
List *allRules = NULL;
if (pBy->partType != PARTTYP_HASH) {
Assert(pElem);
}
if (pBy->partType == PARTTYP_HASH) {
List *colElts = pBy->keys;
ListCell *lc = NULL;
char exprBuf[10000];
StringInfoData sid;
int colcnt = 0;
Node *pHashArgs = NULL;
Node *pExpr = NULL;
PartitionBoundSpec *pBSpec = NULL;
if (pElem) {
pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
}
exprBuf[0] = '\0';
initStringInfo(&sid);
lc = list_head(colElts);
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
Value *pColConst;
ColumnRef *pCRef = NULL;
pColConst = (Value *)pCol;
Assert(IsA(pColConst, String));
if (!deparse_partition_rule((Node *)pCol, exprBuf, sizeof(exprBuf))) {
int loco = pBy->location;
if (pBSpec) loco = pBSpec->location;
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("unknown type or operator%s", at_depth),
parser_errposition(pstate, loco)));
}
pCRef = makeNode(ColumnRef);
pCRef->location = -1;
pCRef->fields = list_make1(pCol);
if (colcnt) {
Node *pAppOp = NULL;
pAppOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "||", pHashArgs,
(Node *)pCRef, -1);
pHashArgs = pAppOp;
appendStringInfo(&sid, "||");
} else {
pHashArgs = (Node *)pCRef;
}
appendStringInfo(&sid, "%s", exprBuf);
colcnt++;
} /* end for all cols */
/* magic_hash: maximum number of partitions is maxPartNum
current partition number is parNumId
*/
/* modulus arithmetic is 0 to N-1, so go to (partNumId-1).
* Also, hashtext can return a negative, so fix it up */
snprintf(exprBuf, sizeof(exprBuf),
/* "magic_hash(%d, %s) = %d", */
/* double % for % literal - ((hash(cols)%max + max) % max) */
"(hashtext(%s)%%%d + %d)%%%d = %d", sid.data, maxPartNum,
maxPartNum, maxPartNum, (partNumId - 1));
pfree(sid.data);
{
Node *pPlusOp = NULL;
Node *pModOp = NULL;
A_Const *pModBy = NULL;
A_Const *pPartID = NULL;
FuncCall *pFC = makeNode(FuncCall);
pFC->funcname = list_make1(makeString("hashtext"));
pFC->args = list_make1(pHashArgs);
pFC->agg_star = FALSE;
pFC->agg_distinct = FALSE;
pFC->location = -1;
pFC->over = NULL;
pModBy = makeNode(A_Const);
pModBy->val.type = T_Integer;
pModBy->val.val.ival = maxPartNum;
pModBy->location = -1;
pPartID = makeNode(A_Const);
pPartID->val.type = T_Integer;
pPartID->val.val.ival = (partNumId - 1);
pPartID->location = -1;
pModOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "%", (Node *)pFC,
(Node *)pModBy, -1);
pPlusOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "+", (Node *)pModOp,
(Node *)pModBy, -1);
pModOp = (Node *)makeSimpleA_Expr(AEXPR_OP, "%", (Node *)pPlusOp,
(Node *)pModBy, -1);
pExpr =
(Node *)makeSimpleA_Expr(AEXPR_OP, "=", pModOp, (Node *)pPartID, -1);
}
/* first the CHECK constraint, then the INSERT statement, then
* the RULE statement
*/
allRules = list_make1(pExpr);
if (doRuleStmt) {
pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, exprBuf, pExpr);
allRules = lappend(allRules, pRule);
pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, exprBuf, pExpr);
allRules = lappend(allRules, pRule);
}
} /* end if HASH */
if (pBy->partType == PARTTYP_LIST) {
Node *pIndAND = NULL;
Node *pIndOR = NULL;
List *colElts = pBy->keys;
ListCell *lc = NULL;
List *valElts = NULL;
ListCell *lc_val = NULL;
ListCell *lc_valent = NULL;
char exprBuf[10000];
char ANDBuf[10000];
char ORBuf[10000];
PartitionValuesSpec *spec = (PartitionValuesSpec *)pElem->boundSpec;
exprBuf[0] = '\0';
ANDBuf[0] = '\0';
ORBuf[0] = '\0';
valElts = spec->partValues;
lc_valent = list_head(valElts);
/* number of values is a multiple of the number of columns */
if (lc_valent) lc_val = list_head((List *)lfirst(lc_valent));
for (; lc_val;) /* for all vals */
{
lc = list_head(colElts);
pIndAND = NULL;
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
Node *pEq = NULL;
Value *pColConst;
A_Const *pValConst;
ColumnRef *pCRef;
bool isnull = false;
pCRef = makeNode(ColumnRef); /* need columnref for WHERE */
if (NULL == lc_val) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("mismatched columns for VALUES%s", at_depth),
parser_errposition(pstate, spec->location)));
}
pColConst = (Value *)pCol;
pValConst = (A_Const *)lfirst(lc_val);
Assert(IsA(pColConst, String));
pCRef->location = -1;
pCRef->fields = list_make1(pColConst);
if (!(IsA(pValConst, A_Const))) {
Const *c = (Const *)pValConst;
Type typ;
Form_pg_type pgtype;
Datum dat;
A_Const *aconst;
Value *val;
Assert(IsA(c, Const));
if (c->constisnull) {
isnull = true;
aconst = NULL;
} else {
aconst = makeNode(A_Const);
typ = typeidType(c->consttype);
pgtype = (Form_pg_type)GETSTRUCT(typ);
dat = OidFunctionCall1(pgtype->typoutput, c->constvalue);
ReleaseType(typ);
val = makeString(DatumGetCString(dat));
aconst->val = *val;
aconst->location = -1;
}
pValConst = aconst;
}
if (isnull) {
NullTest *nt = makeNode(NullTest);
nt->arg = (Expr *)pCRef;
nt->nulltesttype = IS_NULL;
pEq = (Node *)nt;
} else
/* equality expression: column = value */
pEq = (Node *)makeSimpleA_Expr(AEXPR_OP, "=", (Node *)pCRef,
(Node *)pValConst, -1 /* position */);
if (!deparse_partition_rule((Node *)pEq, exprBuf, sizeof(exprBuf))) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("unknown type or operator%s", at_depth),
parser_errposition(pstate, spec->location)));
}
/* for multiple cols - AND the matches eg:
(col = value) AND (col = value)
*/
if (pIndAND) {
char *pfoo = pstrdup(ANDBuf);
pIndAND = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndAND, pEq,
-1 /* position */);
snprintf(ANDBuf, sizeof(ANDBuf), "((%s) and %s)", exprBuf, pfoo);
pfree(pfoo);
} else {
pIndAND = pEq;
snprintf(ANDBuf, sizeof(ANDBuf), "(%s)", exprBuf);
}
lc_val = lnext(lc_val);
} /* end for all cols */
/* if more VALUES than columns, then multiple matching
conditions, so OR them eg:
((col = value) AND (col = value)) OR
((col = value) AND (col = value)) OR
((col = value) AND (col = value))
*/
if (pIndOR) {
char *pfoo = pstrdup(ORBuf);
pIndOR = (Node *)makeA_Expr(AEXPR_OR, NIL, pIndOR, pIndAND,
-1 /* position */);
snprintf(ORBuf, sizeof(ORBuf), "((%s) OR %s)", ANDBuf, pfoo);
pfree(pfoo);
} else {
pIndOR = pIndAND;
snprintf(ORBuf, sizeof(ORBuf), "(%s)", ANDBuf);
}
if (lc_val == NULL) {
lc_valent = lnext(lc_valent);
if (lc_valent) lc_val = list_head((List *)lfirst(lc_valent));
}
} /* end for all vals */
/* first the CHECK constraint, then the INSERT statement, then
* the RULE statement
*/
allRules = list_make1(pIndOR);
if (doRuleStmt) {
pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, ORBuf, pIndOR);
allRules = lappend(allRules, pRule);
pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, ORBuf, pIndOR);
allRules = lappend(allRules, pRule);
}
} /* end if LIST */
/*
For RANGE partitions with an EVERY clause, the following
fields are defined:
int everyOffset - 0 for no EVERY, else 1 to maxEveryOffset
int maxEveryOffset - 1 for no EVERY, else >1
ListCell **pp_lc_anp - the pointer to the pointer
to the ListCell of [A]ll[N]ew[P]artitions,
a list of lists of
stringified values
(as opposed to
A_Const's).
*/
if (pBy->partType == PARTTYP_RANGE) {
Node *pIndAND = NULL;
Node *pIndOR = NULL;
List *colElts = pBy->keys;
ListCell *lc = NULL;
List *valElts = NULL;
ListCell *lc_val = NULL;
PartitionRangeItem *pRI = NULL;
char exprBuf[10000];
char ANDBuf[10000];
char ORBuf[10000];
int range_idx;
PartitionBoundSpec *pBSpec = NULL;
ListCell *lc_every_val = NULL;
List *allNewCols = NIL;
if (pElem) {
pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
}
exprBuf[0] = '\0';
ANDBuf[0] = '\0';
ORBuf[0] = '\0';
pRI = (PartitionRangeItem *)(pBSpec->partStart);
if (pRI)
valElts = pRI->partRangeVal;
else
valElts = NULL;
if (maxEveryOffset > 1) /* if have an EVERY clause */
{
ListCell *lc_anp = NULL;
/* check the list of "all new partitions" */
if (pp_lc_anp) {
lc_anp = *pp_lc_anp;
if (lc_anp) {
allNewCols = lfirst(lc_anp);
/* find the list of columns for a new partition */
if (allNewCols) {
lc_every_val = list_head(allNewCols);
}
}
}
} /* end if every */
/* number of values must equal of the number of columns */
for (range_idx = 0; range_idx < 2; range_idx++) {
char *expr_op = ">";
if (everyOffset > 1) /* for generated START for EVERY */
expr_op = ">="; /* always be inclusive */
else
/* only inclusive set that way */
if (pRI && (PART_EDGE_INCLUSIVE == pRI->partedge))
expr_op = ">=";
if (range_idx) /* Only do it for the upper bound iteration */
{
pRI = (PartitionRangeItem *)(pBSpec->partEnd);
if (pRI)
valElts = pRI->partRangeVal;
else
valElts = NULL;
expr_op = "<";
/* for generated END for EVERY always be exclusive */
if ((everyOffset + 1) < maxEveryOffset)
expr_op = "<";
else
/* only be inclusive if set that way */
if (pRI && (PART_EDGE_INCLUSIVE == pRI->partedge))
expr_op = "<=";
/* If have EVERY, and not the very first START or last END */
if ((0 != everyOffset) && (everyOffset + 1 <= maxEveryOffset)) {
if (*pp_lc_anp) {
if (everyOffset != 1) *pp_lc_anp = lnext(*pp_lc_anp);
if (*pp_lc_anp) {
allNewCols = lfirst(*pp_lc_anp);
if (allNewCols) lc_every_val = list_head(allNewCols);
}
}
}
} /* end if range_idx != 0 */
lc_val = list_head(valElts);
for (; lc_val;) /* for all vals */
{
lc = list_head(colElts);
pIndAND = NULL;
for (; lc; lc = lnext(lc)) /* for all cols */
{
Node *pCol = lfirst(lc);
Node *pEq = NULL;
Value *pColConst;
A_Const *pValConst;
ColumnRef *pCRef;
pCRef = makeNode(ColumnRef); /* need columnref for WHERE */
if (NULL == lc_val) {
char *st_end = "START";
if (range_idx) {
st_end = "END";
}
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("too few columns in %s specification%s",
st_end, at_depth),
parser_errposition(pstate, pBSpec->location)));
}
pColConst = (Value *)pCol;
pValConst = (A_Const *)lfirst(lc_val);
Assert(IsA(pColConst, String));
pCRef->location = -1;
pCRef->fields = list_make1(pColConst);
if (!(IsA(pValConst, A_Const))) {
Const *c = (Const *)pValConst;
Type typ;
Form_pg_type pgtype;
Datum dat;
A_Const *aconst = makeNode(A_Const);
Value *val;
Assert(IsA(c, Const));
typ = typeidType(c->consttype);
pgtype = (Form_pg_type)GETSTRUCT(typ);
dat = OidFunctionCall1(pgtype->typoutput, c->constvalue);
val = makeString(DatumGetCString(dat));
aconst->val = *val;
aconst->location = -1;
ReleaseType(typ);
pValConst = aconst;
}
pEq = (Node *)makeSimpleA_Expr(AEXPR_OP, expr_op, (Node *)pCRef,
(Node *)pValConst, -1 /* position */);
if (!deparse_partition_rule((Node *)pEq, exprBuf, sizeof(exprBuf))) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("unknown type or operator%s", at_depth),
parser_errposition(pstate, pBSpec->location)));
}
/* for multiple cols - AND the matches eg:
(col = value) AND (col = value)
*/
if (pIndAND) {
char *pfoo = pstrdup(ANDBuf);
pIndAND = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndAND, pEq,
-1 /* position */);
snprintf(ANDBuf, sizeof(ANDBuf), "((%s) and %s)", exprBuf, pfoo);
pfree(pfoo);
} else {
pIndAND = pEq;
snprintf(ANDBuf, sizeof(ANDBuf), "(%s)", exprBuf);
}
lc_val = lnext(lc_val);
if (((0 == range_idx) && (everyOffset > 1)) ||
((1 == range_idx) && (everyOffset + 1 < maxEveryOffset))) {
if (lc_every_val) {
lc_every_val = lnext(lc_every_val);
}
}
} /* end for all cols */
/* if more VALUES than columns, then complain
*/
if (lc_val) {
char *st_end = "START";
if (range_idx) {
st_end = "END";
}
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("too many columns in %s specification%s",
st_end, at_depth),
parser_errposition(pstate, pBSpec->location)));
}
if (pIndOR) {
char *pfoo = pstrdup(ORBuf);
/* XXX XXX build an AND for now. But later we
* split this to distinguish START and END
* conditions */
pIndOR = (Node *)makeA_Expr(AEXPR_AND, NIL, pIndOR, pIndAND,
-1 /* position */);
snprintf(ORBuf, sizeof(ORBuf), "((%s) AND %s)", ANDBuf, pfoo);
pfree(pfoo);
} else {
pIndOR = pIndAND;
snprintf(ORBuf, sizeof(ORBuf), "(%s)", ANDBuf);
}
} /* end for all vals */
} /* end for range_idx */
/* first the CHECK constraint, then the INSERT statement, then
* the RULE statement
*/
allRules = list_make1(pIndOR);
if (doRuleStmt) {
pRule = make_prule_catalog(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, ORBuf, pIndOR);
allRules = lappend(allRules, pRule);
pRule = make_prule_rulestmt(pstate, cxt, stmt, partitionBy, pElem,
at_depth, child_name_str, ORBuf, pIndOR);
allRules = lappend(allRules, pRule);
}
} /* end if RANGE */
return allRules;
} /* end make_partition_rules */
/* XXX: major cleanup required. Get rid of gotos at least */
static int partition_range_compare(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, PartitionBy *pBy,
char *at_depth, int partNumber,
char *compare_op, /* =, <, > only */
PartitionRangeItem *pRI1,
PartitionRangeItem *pRI2) {
int rc = -1;
ListCell *lc1 = NULL;
ListCell *lc2 = NULL;
List *cop = lappend(NIL, makeString(compare_op));
if (!pRI1 || !pRI2) /* error */
return rc;
lc1 = list_head(pRI1->partRangeVal);
lc2 = list_head(pRI2->partRangeVal);
L_redoLoop:
for (; lc1 && lc2;) {
Node *n1 = lfirst(lc1);
Node *n2 = lfirst(lc2);
if (!equal(n1, n2)) break;
lc1 = lnext(lc1);
lc2 = lnext(lc2);
}
if (!lc1 && !lc2) /* both empty, so all values are equal */
{
if (strcmp("=", compare_op) == 0)
return 1;
else
return 0;
} else if (lc1 && lc2) /* both not empty, so last value was different */
{
Datum res;
Node *n1 = lfirst(lc1);
Node *n2 = lfirst(lc2);
/* XXX XXX: can't trust equal() code on "typed" data like date
* types (because it compares things like "location") so do a
* real compare using eval .
*/
res = eval_basic_opexpr(pstate, cop, n1, n2, NULL, NULL, NULL, -1);
if (DatumGetBool(res) && strcmp("=", compare_op) == 0) {
/* surprise! they were equal after all. So keep going... */
lc1 = lnext(lc1);
lc2 = lnext(lc2);
goto L_redoLoop;
}
return DatumGetBool(res);
} else if (lc1 || lc2) {
/* lists of different lengths */
if (strcmp("=", compare_op) == 0) return 0;
/* longer list is bigger? */
if (strcmp("<", compare_op) == 0) {
if (lc1)
return 0;
else
return 1;
}
if (strcmp(">", compare_op) == 0) {
if (lc1)
return 1;
else
return 0;
}
}
return rc;
} /* end partition_range_compare */
static int part_el_cmp(void *a, void *b, void *arg) {
RegProcedure **sortfuncs = (RegProcedure **)arg;
PartitionElem *el1 = (PartitionElem *)a;
PartitionElem *el2 = (PartitionElem *)b;
PartitionBoundSpec *bs1;
PartitionBoundSpec *bs2;
int i;
List *start1 = NIL, *start2 = NIL;
ListCell *lc1, *lc2;
/*
* We call this function from all over the place so don't assume that
* things are valid.
*/
if (!el1)
return -1;
else if (!el2)
return 1;
if (el1->isDefault) return -1;
if (el2->isDefault) return 1;
bs1 = (PartitionBoundSpec *)el1->boundSpec;
bs2 = (PartitionBoundSpec *)el2->boundSpec;
if (!bs1) return -1;
if (!bs2) return 1;
if (bs1->partStart)
start1 = ((PartitionRangeItem *)bs1->partStart)->partRangeVal;
if (bs2->partStart)
start2 = ((PartitionRangeItem *)bs2->partStart)->partRangeVal;
if (!start1 && !start2) {
/* these cases are syntax errors but they'll be picked up later */
if (!bs1->partEnd && !bs2->partEnd)
return 0;
else if (!bs1->partEnd)
return 1;
else if (!bs2->partEnd)
return -1;
else {
List *end1 = ((PartitionRangeItem *)bs1->partEnd)->partRangeVal;
List *end2 = ((PartitionRangeItem *)bs2->partEnd)->partRangeVal;
i = 0;
forboth(lc1, end1, lc2, end2) {
Const *c1 = lfirst(lc1);
Const *c2 = lfirst(lc2);
/* use < */
RegProcedure sortFunction = sortfuncs[0][i++];
if (DatumGetBool(
OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
return -1; /* a < b */
if (DatumGetBool(
OidFunctionCall2(sortFunction, c2->constvalue, c1->constvalue)))
return 1;
}
/* equal */
return 0;
}
} else if (!start1 && start2) {
/*
* compare end1 to start2. Whether the end and start are inclusive
* or exclusive is very important here. For example, if
* end1 is exclusive and start2 is inclusive, their being equal means
* that end1 finishes *before* start2.
*/
PartitionRangeItem *pri1 = (PartitionRangeItem *)bs1->partEnd;
PartitionRangeItem *pri2 = (PartitionRangeItem *)bs2->partStart;
List *end1 = pri1->partRangeVal;
PartitionEdgeBounding pe1 = pri1->partedge;
PartitionEdgeBounding pe2 = pri2->partedge;
Assert(pe1 != PART_EDGE_UNSPECIFIED);
Assert(pe2 != PART_EDGE_UNSPECIFIED);
i = 0;
forboth(lc1, end1, lc2, start2) {
Const *c1 = lfirst(lc1);
Const *c2 = lfirst(lc2);
RegProcedure sortFunction;
sortFunction = sortfuncs[0][i];
/* try < first */
if (DatumGetBool(
OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
return -1;
/* see if they're equal */
sortFunction = sortfuncs[1][i];
if (DatumGetBool(
OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue))) {
/* equal, but that might actually mean < */
if (pe1 == PART_EDGE_EXCLUSIVE)
return -1; /* it's less than */
else if (pe1 == PART_EDGE_INCLUSIVE && pe2 == PART_EDGE_EXCLUSIVE)
return -1; /* it's less than */
/* otherwise, they're equal */
} else
return 1;
i++;
}
return 0;
} else if (start1 && !start2) {
/* opposite of above */
return -part_el_cmp(b, a, arg);
} else {
/* we have both starts */
PartitionRangeItem *pri1 = (PartitionRangeItem *)bs1->partStart;
PartitionRangeItem *pri2 = (PartitionRangeItem *)bs2->partStart;
PartitionEdgeBounding pe1 = pri1->partedge;
PartitionEdgeBounding pe2 = pri2->partedge;
i = 0;
forboth(lc1, start1, lc2, start2) {
Const *c1 = lfirst(lc1);
Const *c2 = lfirst(lc2);
/* use < */
RegProcedure sortFunction = sortfuncs[0][i];
if (DatumGetBool(
OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue)))
return -1; /* a < b */
sortFunction = sortfuncs[1][i];
if (DatumGetBool(
OidFunctionCall2(sortFunction, c1->constvalue, c2->constvalue))) {
if (pe1 == PART_EDGE_INCLUSIVE && pe2 == PART_EDGE_EXCLUSIVE)
return -1; /* actually, it was < */
else if (pe1 == PART_EDGE_EXCLUSIVE && pe2 == PART_EDGE_INCLUSIVE)
return 1;
} else
return 1;
i++;
}
}
/* all equal */
return 0;
}
static List *sort_range_elems(List *opclasses, List *elems) {
ListCell *lc;
PartitionElem *clauses;
RegProcedure *sortfuncs[2];
int i;
List *newelems = NIL;
sortfuncs[0] = palloc(list_length(opclasses) * sizeof(RegProcedure));
sortfuncs[1] = palloc(list_length(opclasses) * sizeof(RegProcedure));
i = 0;
foreach (lc, opclasses) {
Oid opclass = lfirst_oid(lc);
Oid opoid = get_opclass_member(opclass, InvalidOid, BTLessStrategyNumber);
/* < first */
sortfuncs[0][i] = get_opcode(opoid);
opoid = get_opclass_member(opclass, InvalidOid, BTEqualStrategyNumber);
sortfuncs[1][i] = get_opcode(opoid);
i++;
}
i = 0;
clauses = palloc(sizeof(PartitionElem) * list_length(elems));
foreach (lc, elems)
clauses[i++] = *(PartitionElem *)lfirst(lc);
qsort_arg(clauses, list_length(elems), sizeof(PartitionElem),
(qsort_arg_comparator)part_el_cmp, (void *)sortfuncs);
for (i = 0; i < list_length(elems); i++)
newelems = lappend(newelems, &clauses[i]);
return newelems;
}
static void preprocess_range_spec(partValidationState *vstate) {
PartitionSpec *spec = (PartitionSpec *)vstate->pBy->partSpec;
ParseState *pstate = vstate->pstate;
ListCell *lc;
List *plusop = list_make2(makeString("pg_catalog"), makeString("+"));
List *ltop = list_make2(makeString("pg_catalog"), makeString("<"));
List *coltypes = NIL;
List *stenc;
List *newelts = NIL;
foreach (lc, vstate->pBy->keys) {
char *colname = strVal(lfirst(lc));
bool found = false;
ListCell *lc2;
TypeName *typname = NULL;
foreach (lc2, vstate->cxt->columns) {
ColumnDef *column = lfirst(lc2);
if (strcmp(column->colname, colname) == 0) {
found = true;
if (!OidIsValid(column->typname->typid)) {
Type type = typenameType(vstate->pstate, column->typname);
column->typname->typid = typeTypeId(type);
ReleaseType(type);
}
typname = column->typname;
break;
}
}
Assert(found);
coltypes = lappend(coltypes, typname);
}
partition_range_every(pstate, vstate->pBy, coltypes, vstate->at_depth,
vstate);
/*
* Must happen after partition_range_every(), since that gathers up
* encoding clauses for us.
*/
stenc = spec->enc_clauses;
/*
* Iterate over the elements in a given partition specification and
* expand any EVERY clauses into raw elements.
*/
foreach (lc, spec->partElem) {
PartitionElem *el = lfirst(lc);
PartitionBoundSpec *pbs = (PartitionBoundSpec *)el->boundSpec;
Node *pStoreAttr = NULL;
ListCell *lc2;
bool bTablename = false;
if (IsA(el, ColumnReferenceStorageDirective)) {
stenc = lappend(stenc, el);
continue;
}
/* we might not have a boundary spec if user just specified DEFAULT */
if (!pbs) {
newelts = lappend(newelts, el);
continue;
}
pbs = (PartitionBoundSpec *)transformExpr(pstate, (Node *)pbs);
pStoreAttr = el->storeAttr;
/* MPP-6297: check for WITH (tablename=name) clause
* [only for dump/restore, set deep in the guts of
* partition_range_every...]
*/
bTablename = (NULL != pbs->pWithTnameStr);
if (!bTablename && pbs->partEvery) {
/* the start expression might come from a previous end
* expression
*/
List *start = NIL;
List *curstart = NIL;
List *end = ((PartitionRangeItem *)pbs->partEnd)->partRangeVal;
List *newend = NIL;
List *every =
(List *)((PartitionRangeItem *)pbs->partEvery)->partRangeVal;
List *everyinc;
bool lessthan = true;
int everycount = 0;
List *everyelts = NIL;
bool first = true;
List *everytypes = NIL;
int i;
Assert(pbs->partStart);
Assert(pbs->partEnd);
Assert(IsA(end, List));
Assert(IsA(every, List));
/* we modify every, so copy it */
everyinc = copyObject(every);
while (lessthan) {
Datum res;
Oid restypid;
PartitionElem *newel;
PartitionBoundSpec *newbs;
PartitionRangeItem *newri;
ListCell *lctypes, *lceveryinc;
ListCell *lcstart, *lcend, *lcevery, *lccol, *lclastend;
List *lastend = NIL;
/*
* Other parts of the parser want to know how many clauses
* we expanded here.
*/
everycount++;
if (start == NIL) {
start = ((PartitionRangeItem *)pbs->partStart)->partRangeVal;
lcend = list_head(end);
/* coerce to target type */
forboth(lcstart, start, lccol, coltypes) {
TypeName *typ = lfirst(lccol);
Node *newnode;
newnode = coerce_partition_value(lfirst(lcstart), typ->typid,
typ->typmod, PARTTYP_RANGE);
lfirst(lcstart) = newnode;
/* be sure we coerce the end value */
newnode = coerce_partition_value(lfirst(lcend), typ->typid,
typ->typmod, PARTTYP_RANGE);
lfirst(lcend) = newnode;
lcend = lnext(lcend);
}
curstart = copyObject(start);
forboth(lccol, coltypes, lcevery, every) {
TypeName *typ = lfirst(lccol);
HeapTuple optup;
List *opname =
list_make2(makeString("pg_catalog"), makeString("+"));
Node *e = lfirst(lcevery);
Oid rtypeId = exprType(e);
Oid newrtypeId;
/* first, make sure we can build up an operator */
optup = oper(pstate, opname, typ->typid, rtypeId, true, -1);
if (!HeapTupleIsValid(optup))
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"could not identify operator for partitioning operation "
"between type \"%s\" and type \"%s\"",
format_type_be(typ->typid), format_type_be(rtypeId)),
errhint(
"Add an explicit cast to the partitioning parameters")));
newrtypeId = ((Form_pg_operator)GETSTRUCT(optup))->oprright;
ReleaseOperator(optup);
if (rtypeId != newrtypeId) {
Type newetyp = typeidType(newrtypeId);
int4 typmod = ((Form_pg_type)GETSTRUCT(newetyp))->typtypmod;
ReleaseType(newetyp);
/* we need to coerce */
e = coerce_partition_value(e, newrtypeId, typmod, PARTTYP_RANGE);
lfirst(lcevery) = e;
rtypeId = newrtypeId;
}
everytypes = lappend_oid(everytypes, rtypeId);
}
} else {
curstart = newend;
lastend = newend;
newend = NIL;
}
lcend = list_head(end);
lcevery = list_head(everyinc);
lclastend = list_head(lastend);
forboth(lcstart, start, lccol, coltypes) {
Const *mystart = lfirst(lcstart);
TypeName *type = lfirst(lccol);
Const *myend;
Const *clauseend;
Const *clauseevery;
Oid typid = type->typid;
int16 len = get_typlen(typid);
bool typbyval = get_typbyval(typid);
Assert(lcevery);
Assert(lcend);
clauseevery = lfirst(lcevery);
clauseend = lfirst(lcend);
/* add the every value to the start */
res = eval_basic_opexpr(pstate, plusop, (Node *)mystart,
(Node *)clauseevery, &typbyval, &len, &typid,
-1);
/* XXX: typmod ? */
myend = makeConst(type->typid, type->typmod, len,
datumCopy(res, typbyval, len), false, typbyval);
/* make sure res is bigger than the last value */
if (lclastend) {
Oid typ = InvalidOid;
Const *prevval = (Const *)lfirst(lclastend);
Datum is_lt =
eval_basic_opexpr(pstate, ltop, (Node *)prevval, (Node *)myend,
NULL, NULL, &typ, -1);
if (!DatumGetBool(is_lt)) elog(ERROR, "every interval too small");
}
restypid = InvalidOid;
res = eval_basic_opexpr(pstate, ltop, (Node *)myend,
(Node *)clauseend, NULL, NULL, &restypid, -1);
if (!DatumGetBool(res)) {
newend = end;
lessthan = false;
break;
}
newend = lappend(newend, myend);
lcend = lnext(lcend);
lcevery = lnext(lcevery);
if (lclastend) lclastend = lnext(lclastend);
}
lctypes = list_head(everytypes);
forboth(lcevery, every, lceveryinc, everyinc) {
Oid typid = lfirst_oid(lctypes);
bool byval = get_typbyval(typid);
int16 typlen = get_typlen(typid);
Const *c;
/* increment every */
res = eval_basic_opexpr(pstate, plusop, (Node *)lfirst(lcevery),
(Node *)lfirst(lceveryinc), NULL, NULL,
&typid, -1);
c = makeConst(typid, -1, typlen, res, false, byval);
pfree(lfirst(lceveryinc));
lfirst(lceveryinc) = c;
}
newel = makeNode(PartitionElem);
newel->subSpec = copyObject(el->subSpec);
newel->storeAttr = copyObject(el->storeAttr);
newel->AddPartDesc = copyObject(el->AddPartDesc);
newel->location = el->location;
newbs = makeNode(PartitionBoundSpec);
newel->boundSpec = (Node *)newbs;
/* start */
newri = makeNode(PartitionRangeItem);
/* modifier only relevant on the first iteration */
if (everycount == 1)
newri->partedge = ((PartitionRangeItem *)pbs->partStart)->partedge;
else
newri->partedge = PART_EDGE_INCLUSIVE;
newri->location = ((PartitionRangeItem *)pbs->partStart)->location;
newri->partRangeVal = curstart;
newbs->partStart = (Node *)newri;
/* end */
newri = makeNode(PartitionRangeItem);
newri->partedge = PART_EDGE_EXCLUSIVE;
newri->location = ((PartitionRangeItem *)pbs->partEnd)->location;
newri->partRangeVal = newend;
newbs->partEnd = (Node *)newri;
/* every */
newbs->partEvery = (Node *)copyObject(pbs->partEvery);
newbs->location = pbs->location;
newbs->everyGenList = pbs->everyGenList;
everyelts = lappend(everyelts, newel);
if (first) first = false;
}
/*
* Update the final PartitionElem's partEnd modifier if it isn't
* the default
*/
if (((PartitionRangeItem *)pbs->partEnd)->partedge !=
PART_EDGE_EXCLUSIVE) {
PartitionElem *elem = lfirst(list_tail(everyelts));
PartitionBoundSpec *s = (PartitionBoundSpec *)elem->boundSpec;
PartitionRangeItem *ri = (PartitionRangeItem *)s->partEnd;
ri->partedge = ((PartitionRangeItem *)pbs->partEnd)->partedge;
}
/* add everycount to each EVERY clause */
i = 0;
foreach (lc2, everyelts) {
PartitionElem *el2 = lfirst(lc2);
PartitionBoundSpec *pbs = (PartitionBoundSpec *)el2->boundSpec;
PartitionRangeItem *ri = (PartitionRangeItem *)pbs->partEvery;
ri->everycount = everycount;
/*
* Generate the new name, which is parname + every count
* but only if every expands to more than one partition.
*/
if (el->partName) {
char newname[sizeof(NameData) + 10];
if (list_length(everyelts) > 1)
snprintf(newname, sizeof(newname), "%s_%u", strVal(el->partName),
++i);
else
snprintf(newname, sizeof(newname), "%s", strVal(el->partName));
if (strlen(newname) > NAMEDATALEN)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("partition name \"%s\" too long",
strVal(el->partName)),
parser_errposition(vstate->pstate, el->location)));
el2->partName = (Node *)makeString(pstrdup(newname));
}
}
newelts = list_concat(newelts, everyelts);
} else {
if (pbs->partStart) {
ListCell *lccol;
ListCell *lcstart;
List *start = ((PartitionRangeItem *)pbs->partStart)->partRangeVal;
/* coerce to target type */
forboth(lcstart, start, lccol, coltypes) {
Node *mystart = lfirst(lcstart);
TypeName *typ = lfirst(lccol);
Node *newnode;
newnode = coerce_partition_value(mystart, typ->typid, typ->typmod,
PARTTYP_RANGE);
lfirst(lcstart) = newnode;
}
}
if (pbs->partEnd) {
List *end = ((PartitionRangeItem *)pbs->partEnd)->partRangeVal;
ListCell *lccol;
ListCell *lcend;
/* coerce to target type */
forboth(lcend, end, lccol, coltypes) {
Node *myend = lfirst(lcend);
TypeName *typ = lfirst(lccol);
Node *newnode;
newnode = coerce_partition_value(myend, typ->typid, typ->typmod,
PARTTYP_RANGE);
lfirst(lcend) = newnode;
}
}
newelts = lappend(newelts, el);
}
}
/* do an initial sort */
spec->partElem = sort_range_elems(vstate->pBy->keyopclass, newelts);
spec->enc_clauses = stenc;
}
static bool range_partition_walker(Node *node, void *context) {
range_partition_ctx *ctx = (range_partition_ctx *)context;
if (node == NULL)
return false;
else if (IsA(node, Const)) {
Const *c = (Const *)node;
if (c->constisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot use NULL with range partition specification"),
errOmitLocation(true),
parser_errposition(ctx->pstate, ctx->location)));
return false;
} else if (IsA(node, A_Const)) {
A_Const *c = (A_Const *)node;
if (IsA(&c->val, Null))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot use NULL with range partition specification"),
errOmitLocation(true),
parser_errposition(ctx->pstate, ctx->location)));
return false;
}
return expression_tree_walker(node, range_partition_walker, ctx);
}
void PartitionRangeItemIsValid(ParseState *pstate, PartitionRangeItem *pri) {
ListCell *lc;
range_partition_ctx ctx;
if (!pri) return;
ctx.pstate = pstate;
ctx.location = pri->location;
foreach (lc, pri->partRangeVal) { range_partition_walker(lfirst(lc), &ctx); }
}
/*
* Basic partition validation:
* Check that PARTITIONS matches specification (for HASH). Perform basic error
* checking on boundary specifications.
*/
static void validate_range_partition(partValidationState *vstate) {
bool bAppendRange = false;
PartitionBoundSpec *prevBSpec = NULL;
PartitionBoundSpec *spec = (PartitionBoundSpec *)vstate->spec;
vstate->spec = (Node *)spec;
if (spec) {
/* XXX: create a type2name function */
char *specTName = "LIST";
if (IsA(spec, PartitionValuesSpec)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of %s boundary "
"specification in partition clause",
specTName),
/* MPP-4249: use value spec location if have one */
errOmitLocation(true),
parser_errposition(vstate->pstate,
((PartitionValuesSpec *)spec)->location)));
}
} else {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("missing boundary specification"), errOmitLocation(true),
parser_errposition(vstate->pstate, vstate->pElem->location)));
}
{
/* if the previous partition was named, and
* current is not, and the prev and current
* use single, complementary START/END specs,
* then complain.
*/
/* must have a valid RANGE pBSpec now or else
* would have error'd out...
*/
int currStartEnd = 0;
if (spec->partStart) currStartEnd += 1;
if (spec->partEnd) currStartEnd += 2;
/* Note: for first loop, prevStartEnd = 0, so
* this test is ok */
if (((vstate->prevHadName && !(vstate->pElem->partName)) ||
(!vstate->prevHadName && vstate->pElem->partName)) &&
(((vstate->prevStartEnd == 1) && (currStartEnd == 2)) ||
((vstate->prevStartEnd == 2) && (currStartEnd == 1)))) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of mixed named and "
"unnamed RANGE boundary "
"specifications%s",
vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
vstate->prevStartEnd = currStartEnd;
}
vstate->prevHadName = !!(vstate->pElem->partName); /* bool t/f */
if (spec->partStart)
PartitionRangeItemIsValid(vstate->pstate,
(PartitionRangeItem *)spec->partStart);
if (spec->partEnd)
PartitionRangeItemIsValid(vstate->pstate,
(PartitionRangeItem *)spec->partEnd);
/*
* Fixup boundaries for previous partition ending if necessary
*/
if (!vstate->prevElem) {
bAppendRange = true;
goto L_setprevElem;
}
prevBSpec = (PartitionBoundSpec *)vstate->prevElem->boundSpec;
/* XXX XXX: can check for overlap here too */
if (!prevBSpec) {
/* XXX XXX: if the previous partition declaration
* does not have a boundary spec then its end
* needs to be the start of the current */
bAppendRange = true;
goto L_setprevElem;
}
if (spec->partStart) {
if (!prevBSpec->partEnd) {
PartitionRangeItem *pRI = (PartitionRangeItem *)(spec->partStart);
PartitionRangeItem *prevRI = makeNode(PartitionRangeItem);
if (prevBSpec->partStart) {
int compareRc = 0;
PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
PartitionRangeItem *pRI2 = (PartitionRangeItem *)prevBSpec->partStart;
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "<", pRI2, pRI1);
if (1 != compareRc) {
/* XXX: better message */
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"START of partition%s less "
"than START of previous%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
}
prevRI->location = pRI->location;
prevRI->partRangeVal = list_copy(pRI->partRangeVal);
/* invert sense of inclusiveness */
prevRI->partedge = (PART_EDGE_INCLUSIVE == pRI->partedge)
? PART_EDGE_EXCLUSIVE
: PART_EDGE_INCLUSIVE;
prevBSpec->partEnd = (Node *)prevRI;
/* don't need to check if range overlaps */
bAppendRange = true;
} else if (0) /* XXX XXX XXX XXX */
{
/* else if overlap complain */
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("start of partition%s overlaps previous%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
} /* end if current partStart */
else /* no start, so use END of previous */
{
if (!prevBSpec->partEnd) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"cannot derive starting value of "
"partition%s based upon ending of "
"previous%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
} else /* build ri for current */
{
PartitionRangeItem *pRI = makeNode(PartitionRangeItem);
PartitionRangeItem *prevRI = (PartitionRangeItem *)(prevBSpec->partEnd);
pRI->location = prevRI->location;
pRI->partRangeVal = list_copy(prevRI->partRangeVal);
/* invert sense of inclusiveness */
pRI->partedge = (PART_EDGE_INCLUSIVE == prevRI->partedge)
? PART_EDGE_EXCLUSIVE
: PART_EDGE_INCLUSIVE;
spec->partStart = (Node *)pRI;
/* don't need to check if range overlaps */
bAppendRange = true;
} /* end build ri for current */
} /* end use END of previous */
L_setprevElem:
/* check for overlap,
* then add new partitions to sorted list
*/
if (spec->partStart && spec->partEnd) {
int compareRc = 0;
PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
PartitionRangeItem *pRI2 = (PartitionRangeItem *)spec->partEnd;
PartitionRangeItemIsValid(vstate->pstate,
(PartitionRangeItem *)spec->partEnd);
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, ">", pRI1, pRI2);
if (0 != compareRc) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("START greater than END for partition%s%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);
if (0 != compareRc) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("START equal to END for partition%s%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
}
if (NIL == vstate->allRangeVals) bAppendRange = true;
/* check previous first */
if (!bAppendRange && (spec->partStart && prevBSpec && prevBSpec->partEnd)) {
/* compare to last */
int rc = 0;
PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
PartitionRangeItem *pRI2 = (PartitionRangeItem *)prevBSpec->partEnd;
rc = partition_range_compare(vstate->pstate, vstate->cxt, vstate->stmt,
vstate->pBy, vstate->at_depth,
vstate->partNumber, "=", pRI1, pRI2);
if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
(pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == rc)) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"starting value of partition%s "
"overlaps previous range%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
/*
* If values are equal, but not inclusive to both,
* then append is possible
*/
if (1 != rc)
rc = partition_range_compare(vstate->pstate, vstate->cxt, vstate->stmt,
vstate->pBy, vstate->at_depth,
vstate->partNumber, ">", pRI1, pRI2);
if (1 == rc) bAppendRange = 1;
if (rc != 1) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"starting value of partition%s "
"overlaps previous range%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
} /* end compare to last */
if (bAppendRange) {
vstate->allRangeVals = lappend(vstate->allRangeVals, spec);
} else { /* find position for current range */
ListCell *lc_all = list_head(vstate->allRangeVals);
ListCell *lc_allPrev = NULL;
int compareRc = 0;
/* set up pieces of error messages */
char *currPartRI = "Ending";
char *otherPartPos = "next";
/* linear search sorted list of range specs:
While the current start key is >= loop val
start key advance.
If current start key < loop val start key,
then see if curr start key < *previous* loop
val end key, ie falls in previous range.
If so, error out, else splice current range in
after previous.
*/
for (; lc_all; lc_all = lnext(lc_all)) /* for lc_all */
{
PartitionBoundSpec *lcBSpec = (PartitionBoundSpec *)lfirst(lc_all);
PartitionRangeItem *pRI1 = (PartitionRangeItem *)spec->partStart;
PartitionRangeItem *pRI2 = (PartitionRangeItem *)lcBSpec->partStart;
Assert(spec->partStart);
if (lcBSpec->partStart) {
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);
if (-1 == compareRc) break;
if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
(pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
currPartRI = "Starting";
otherPartPos = "previous";
compareRc = -2;
break;
}
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "<", pRI1, pRI2);
} else
/* if first range spec has no start
* (ie start=MINVALUE) then current start
* must be after it
*/
compareRc = 0; /* current > MINVALUE */
if (-1 == compareRc) break;
if (1 == compareRc) /* if curr less than loop val */
{
/* curr start is less than loop val, so
* check that curr end is less than loop
* val start (including equality case)
*/
pRI1 = (PartitionRangeItem *)spec->partEnd;
if (!pRI1) {
/* if current start is less than
* loop val start but current end is
* unterminated (ie ending=MAXVALUE)
* then it must overlap
*/
currPartRI = "Ending";
otherPartPos = "next";
compareRc = -2;
break;
}
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "=", pRI2, pRI1);
if (-1 == compareRc) break;
if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
(pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
currPartRI = "Ending";
otherPartPos = "next";
compareRc = -2;
break;
}
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "<", pRI2, pRI1);
if (-1 == compareRc) break;
if (1 == compareRc) {
currPartRI = "Ending";
otherPartPos = "next";
compareRc = -2;
break;
}
/* if current is less than loop val and no
* previous, then append vstate->allRangeVals to
* the current, instead of vice versa
*/
if (!lc_allPrev) break;
lcBSpec = (PartitionBoundSpec *)lfirst(lc_allPrev);
pRI2 = (PartitionRangeItem *)lcBSpec->partEnd;
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "=", pRI1, pRI2);
if (-1 == compareRc) break;
if ((pRI2->partedge == PART_EDGE_INCLUSIVE) &&
(pRI1->partedge == PART_EDGE_INCLUSIVE) && (1 == compareRc)) {
currPartRI = "Starting";
otherPartPos = "previous";
compareRc = -2;
break;
}
compareRc = partition_range_compare(
vstate->pstate, vstate->cxt, vstate->stmt, vstate->pBy,
vstate->at_depth, vstate->partNumber, "<", pRI1, pRI2);
if (-1 == compareRc) break;
if (1 == compareRc) {
currPartRI = "Starting";
otherPartPos = "previous";
compareRc = -2;
break;
}
} /* end if curr less than loop val */
lc_allPrev = lc_all;
} /* end for lc_all */
if (-1 == compareRc) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid range comparison for partition%s%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
if (-2 == compareRc) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("%s value of partition%s overlaps %s range%s", currPartRI,
vstate->namBuf, otherPartPos, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
if (lc_allPrev) {
/* append cell returns new cell, not list */
lappend_cell(vstate->allRangeVals, lc_allPrev, spec);
} else /* no previous, so current is start */
vstate->allRangeVals =
list_concat(list_make1(spec), vstate->allRangeVals);
} /* end find position for current range */
vstate->prevElem = vstate->pElem;
}
Node *coerce_partition_value(Node *node, Oid typid, int32 typmod,
PartitionByType partype) {
Node *out;
/* If it's a NULL, just directly coerce the value */
if (IsA(node, Const)) {
Const *c = (Const *)node;
if (c->constisnull) {
c->consttype = typid;
return node;
}
}
/*
* We want to cast things directly to the table type. We do
* not want to have to store a node which coerces this, it's
* unnecessarily expensive to do it every time.
*/
out = coerce_to_target_type(NULL, node, exprType(node), typid, typmod,
COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
/* MPP-3626: better error message */
if (!out) {
char exprBuf[10000];
char *specTName = "";
char *pparam = "";
StringInfoData sid;
/* elog(ERROR, "cannot coerce partition parameter to column type");
*/
switch (partype) {
case PARTTYP_HASH:
specTName = "HASH ";
break;
case PARTTYP_LIST:
specTName = "LIST ";
break;
case PARTTYP_RANGE:
specTName = "RANGE ";
break;
default:
break;
}
/* try to build a printable string of the node value */
pparam = deparse_expression(
node, deparse_context_for("partition", InvalidOid), false, false);
if (pparam) {
initStringInfo(&sid);
appendStringInfo(&sid, "(");
appendStringInfoString(&sid, pparam);
appendStringInfo(&sid, ") ");
pfree(pparam);
exprBuf[0] = '\0';
snprintf(exprBuf, sizeof(exprBuf), "%s", sid.data);
pfree(sid.data);
pparam = exprBuf;
} else
pparam = "";
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(
"cannot coerce %spartition parameter %s"
"to column type (%s)",
specTName, pparam, format_type_be(typid)),
errOmitLocation(true)));
}
/* explicit coerce */
if (IsA(out, FuncExpr) || IsA(out, OpExpr) || IsA(out, CoerceToDomain)) {
bool isnull;
Datum d = partition_arg_get_val(out, &isnull);
Const *c;
Type typ = typeidType(typid);
pfree(out);
c = makeConst(typid, -1, typeLen(typ), d, isnull, typeByVal(typ));
ReleaseType(typ);
out = (Node *)c;
/*
* coerce again for typmod: if we can't coerce the type mod,
* we'll error out
*/
out = coerce_to_target_type(NULL, out, exprType(out), typid, typmod,
COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
/* be careful, we might just add the coercion function back in! */
if (IsA(out, FuncExpr) || IsA(out, OpExpr)) out = (Node *)c;
} else
Assert(IsA(out, Const));
return out;
}
static void validate_list_partition(partValidationState *vstate) {
PartitionValuesSpec *spec;
Node *n = vstate->spec;
ListCell *lc;
List *coltypes = NIL;
/* just recreate attnum references */
foreach (lc, vstate->pBy->keys) {
ListCell *llc2;
char *colname = strVal(lfirst(lc));
bool found = false;
TypeName *typname = NULL;
foreach (llc2, vstate->cxt->columns) {
ColumnDef *column = lfirst(llc2);
if (strcmp(column->colname, colname) == 0) {
found = true;
if (!OidIsValid(column->typname->typid)) {
Type type = typenameType(vstate->pstate, column->typname);
column->typname->typid = typeTypeId(type);
ReleaseType(type);
}
typname = column->typname;
break;
}
}
Assert(found);
coltypes = lappend(coltypes, typname);
}
if (!PointerIsValid(n)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"missing boundary specification in "
"partition%s of type LIST%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, vstate->pElem->location)));
}
if (!IsA(n, PartitionValuesSpec)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid boundary specification for LIST partition"),
errOmitLocation(true),
parser_errposition(vstate->pstate, vstate->pElem->location)));
}
spec = (PartitionValuesSpec *)n;
if (spec->partValues) {
ListCell *lc;
List *newvals = NIL;
foreach (lc, spec->partValues) {
ListCell *lc_val = NULL;
List *vals = lfirst(lc);
List *tvals = NIL; /* transformed clause */
int nvals;
int nparts = list_length(vstate->pBy->keys);
ListCell *llc2;
nvals = list_length(vals);
/* Number of values should be same as specified partition keys */
if (nvals != nparts) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"partition key has %i column%s but %i column%s "
"specified in VALUES clause",
list_length(vstate->pBy->keys),
list_length(vstate->pBy->keys) ? "s" : "", nvals,
nvals ? "s" : ""),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
/*
* Transform expressions
*/
llc2 = list_head(coltypes);
foreach (lc_val, vals) {
Node *node = transformExpr(vstate->pstate, (Node *)lfirst(lc_val));
TypeName *type = lfirst(llc2);
node = coerce_partition_value(node, type->typid, type->typmod,
PARTTYP_LIST);
tvals = lappend(tvals, node);
if (lnext(llc2))
llc2 = lnext(llc2);
else
llc2 = list_head(coltypes); /* circular */
}
Assert(list_length(tvals) == nvals);
/*
* Check for duplicate keys
*/
foreach (lc_val, vstate->allListVals) /* dups across all specs */
{
List *already = lfirst(lc_val);
ListCell *lc2;
foreach (lc2, already) {
List *item = lfirst(lc2);
Assert(IsA(item, List) && list_length(item) == nvals);
/*
* Re MPP-17814
* The lists tvals and item each represent a tuple of nvals
* attribute values. If they are equal, the new value (tvals)
* is in vstate->allListVals, i.e., tvals has been seen before.
*/
if (equal(tvals, item)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"duplicate VALUES "
"in partition%s%s",
vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
parser_errposition(vstate->pstate, spec->location)));
}
}
}
newvals = list_append_unique(newvals, tvals);
}
vstate->allListVals = lappend(vstate->allListVals, newvals);
spec->partValues = newvals;
} /* end if spec partvalues */
} /* end validate_list_partition */
static List *transformPartitionStorageEncodingClauses(List *enc) {
ListCell *lc;
List *out = NIL;
/*
* Test that directives at different levels of subpartitioning do not
* conflict. A conflict would be the case where a directive appears for the
* same column but different parameters have been supplied.
*/
foreach (lc, enc) {
ListCell *in;
ColumnReferenceStorageDirective *a = lfirst(lc);
bool add = true;
Insist(IsA(a, ColumnReferenceStorageDirective));
foreach (in, out) {
ColumnReferenceStorageDirective *b = lfirst(in);
Insist(IsA(b, ColumnReferenceStorageDirective));
if (lc == in) continue;
if (equal(a->column, b->column)) {
if (!equal(a->encoding, b->encoding))
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"conflicting ENCODING clauses for column "
"\"%s\"",
strVal(a->column))));
/*
* We found an identical directive on the same column. You'd
* think we should blow up but this is caused by recursive
* expansion of partitions. Anyway, only add it once.
*/
add = false;
}
}
if (add) out = lappend(out, a);
}
/* validate and transform each encoding clauses */
foreach (lc, out) {
ColumnReferenceStorageDirective *c = lfirst(lc);
c->encoding = transformStorageEncodingClause(c->encoding);
}
return out;
}
static void split_encoding_clauses(List *encs, List **non_def,
ColumnReferenceStorageDirective **def) {
ListCell *lc;
foreach (lc, encs) {
ColumnReferenceStorageDirective *c = lfirst(lc);
Insist(IsA(c, ColumnReferenceStorageDirective));
if (c->deflt) {
if (*def)
elog(ERROR,
"DEFAULT COLUMN ENCODING clause specified more than "
"once for partition");
*def = c;
} else
*non_def = lappend(*non_def, c);
}
}
static void merge_partition_encoding(ParseState *pstate, PartitionElem *elem,
List *penc) {
List *elem_nondefs = NIL;
List *part_nondefs = NIL;
ColumnReferenceStorageDirective *elem_def = NULL;
ColumnReferenceStorageDirective *part_def = NULL;
ListCell *lc;
AlterPartitionCmd *pc;
/*
* First of all, we shouldn't proceed if this partition isn't AOCO
*/
/*
* Yes, I am as surprised as you are that this is how we represent the WITH
* clause here.
*/
pc = (AlterPartitionCmd *)elem->storeAttr;
if (pc) {
if (elem->colencs)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"ENCODING clause only supported with "
"column oriented partitions"),
parser_errposition(pstate, elem->location)));
else
return; /* nothing more to do */
}
/*
* If the specific partition has no specific column encoding, just
* set it to the partition level default and we're done.
*/
if (!elem->colencs) {
elem->colencs = penc;
return;
}
/*
* Fixup the actual column encoding clauses for this specific partition
* element.
*
* Rules:
*
* 1. If an element level clause mentions a specific column, do not override
* it.
* 2. Clauses at the partition configuration level which mention a column
* not already mentioned at the element level, are applied to the element.
* 3. If an element level default clause exists, we're done.
* 4. If a partition configuration level default clause exists, apply it to
* the element level.
* 5. We're done.
*/
/* Split specific clauses and default clauses from both our lists */
split_encoding_clauses(elem->colencs, &elem_nondefs, &elem_def);
split_encoding_clauses(penc, &part_nondefs, &part_def);
/* Add clauses from part_nondefs if the columns are not already mentioned */
foreach (lc, part_nondefs) {
ListCell *lc2;
ColumnReferenceStorageDirective *pd = lfirst(lc);
bool found = false;
foreach (lc2, elem_nondefs) {
ColumnReferenceStorageDirective *ed = lfirst(lc2);
if (equal(pd->column, ed->column)) {
found = true;
break;
}
}
if (!found) elem->colencs = lappend(elem->colencs, pd);
}
if (elem_def) return;
if (part_def) elem->colencs = lappend(elem->colencs, part_def);
}
int validate_partition_spec(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, PartitionBy *pBy,
char *at_depth, int partNumber) {
PartitionSpec *pSpec;
char namBuf[NAMEDATALEN];
List *partElts;
ListCell *lc = NULL;
List *allPartNames = NIL;
partValidationState *vstate;
PartitionElem *pDefaultElem = NULL;
int partno = 0;
List *enc_cls = NIL;
vstate = palloc0(sizeof(partValidationState));
vstate->pstate = pstate;
vstate->cxt = cxt;
vstate->stmt = stmt;
vstate->pBy = pBy;
vstate->at_depth = at_depth;
vstate->partNumber = partNumber;
Assert(pBy);
pSpec = (PartitionSpec *)pBy->partSpec;
/*
* Find number of partitions in the specification, and match it up
* with the PARTITIONS clause if partitioned by HASH, and
* determine the subpartition specifications. Perform basic error
* checking on boundary specifications.
*/
/* NOTE: a top-level PartitionSpec never has a subSpec, but a
* PartitionSpec derived from an inline SubPartition
* specification might have one
*/
/* track previous boundary spec to check for this subtle problem:
(
partition aa start (2007,1) end (2008,2),
partition bb start (2008,2) end (2009,3)
);
This declaration would create four partitions:
1. named aa, starting at 2007, 1
2. unnamed, ending at 2008, 2
3. named bb, starting at 2008, 2
4. unnamed, ending at 2009, 3
Warn user if they do this, since they probably wanted
1. named aa, starting at 2007, 1 and ending at 2008, 2
2. named bb, starting at 2008, 2 and ending at 2009, 3
The extra comma between the start and end is the problem.
*/
if (pBy->partType == PARTTYP_RANGE) preprocess_range_spec(vstate);
partElts = pSpec->partElem;
/*
* If this is a RANGE partition, we might have gleaned some encoding clauses
* already, because preprocess_range_spec() looks at partition elements.
*/
enc_cls = pSpec->enc_clauses;
foreach (lc, partElts) {
PartitionElem *pElem = (PartitionElem *)lfirst(lc);
PartitionBoundSpec *pBSpec = NULL;
if (!IsA(pElem, PartitionElem)) {
Insist(IsA(pElem, ColumnReferenceStorageDirective));
enc_cls = lappend(enc_cls, lfirst(lc));
continue;
}
vstate->pElem = pElem;
if (pSpec->istemplate && pElem->colencs)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"partition specific ENCODING clause not supported in "
"SUBPARTITION TEMPLATE"),
parser_errposition(pstate, pElem->location)));
/*
* We've done all possible expansions so now number the
* partition elements so that we can set the position when
* adding the configuration to the catalog.
*/
pElem->partno = ++partno;
if (pElem) {
pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
vstate->spec = (Node *)pBSpec;
/* handle all default partition cases:
HASH partitioned tables cannot have DEFAULT partitions.
Can only have a single DEFAULT partition.
Default partitions cannot have a boundary specification.
*/
if (pElem->isDefault) {
Assert(pElem->partName); /* default partn must have a name */
snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));
if (pDefaultElem) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"multiple default partitions are not "
"allowed"),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
}
pDefaultElem = pElem; /* save the default */
if (PARTTYP_HASH == pBy->partType) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of DEFAULT partition "
"for partition%s of type HASH%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
}
if (pBSpec) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of boundary specification "
"for DEFAULT partition%s%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
}
} /* end if is default */
if (pElem->partName) {
bool doit = true;
snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));
/*
* We might have expanded an EVERY clause here. If so, just
* add the first partition name.
*/
if (doit) {
if (allPartNames && list_member(allPartNames, pElem->partName)) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"duplicate partition name "
"for partition%s%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
}
allPartNames = lappend(allPartNames, pElem->partName);
}
} else {
if (pElem->AddPartDesc)
snprintf(namBuf, sizeof(namBuf), "%s", pElem->AddPartDesc);
else
snprintf(namBuf, sizeof(namBuf), " number %d", partno);
}
} else {
if (pElem->AddPartDesc)
snprintf(namBuf, sizeof(namBuf), "%s", pElem->AddPartDesc);
else
snprintf(namBuf, sizeof(namBuf), " number %d", partno);
}
/* don't have to validate default partition boundary specs */
if (pElem->isDefault) continue;
vstate->namBuf = namBuf;
switch (pBy->partType) {
case PARTTYP_HASH:
if (vstate->spec) {
char *specTName = "RANGE";
if (IsA(vstate->spec, PartitionValuesSpec)) specTName = "LIST";
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of %s boundary specification "
"in partition%s of type HASH%s",
specTName, vstate->namBuf, vstate->at_depth),
errOmitLocation(true),
/* MPP-4249: use value spec location if have one */
((IsA(vstate->spec, PartitionValuesSpec))
? parser_errposition(
pstate,
((PartitionValuesSpec *)vstate->spec)->location)
:
/* else use boundspec */
parser_errposition(
pstate,
((PartitionBoundSpec *)vstate->spec)->location))));
}
break;
case PARTTYP_RANGE:
validate_range_partition(vstate);
break;
case PARTTYP_LIST:
validate_list_partition(vstate);
break;
case PARTTYP_REFERENCE: /* for future use... */
default:
ereport(
ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("unknown partition type %d %s", pBy->partType, at_depth),
errOmitLocation(true), parser_errposition(pstate, pBy->location)));
break;
}
} /* end foreach(lc, partElts) */
pSpec->enc_clauses = transformPartitionStorageEncodingClauses(enc_cls);
foreach (lc, partElts) {
PartitionElem *elem = (PartitionElem *)lfirst(lc);
if (!IsA(elem, PartitionElem)) continue;
merge_partition_encoding(pstate, elem, pSpec->enc_clauses);
}
/* validate_range_partition might have changed some boundaries */
if (pBy->partType == PARTTYP_RANGE)
pSpec->partElem = sort_range_elems(pBy->keyopclass, partElts);
if (partNumber > -1 && partno != partNumber)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"PARTITIONS \"%d\" must match \"%d\" elements "
"in specification%s",
partNumber, partno, vstate->at_depth),
errOmitLocation(true), parser_errposition(pstate, pBy->location)));
if (vstate->allRangeVals) list_free(vstate->allRangeVals);
if (allPartNames) list_free(allPartNames);
return partno;
} /* end validate_partition_spec */
static Const *flatten_partition_val(Node *node, Oid target_type) {
if (IsA(node, Const))
return (Const *)node;
else {
Datum res;
Oid curtyp;
Const *c;
Type typ = typeidType(target_type);
int32 typmod = ((Form_pg_type)GETSTRUCT(typ))->typtypmod;
int16 typlen = ((Form_pg_type)GETSTRUCT(typ))->typlen;
bool typbyval = ((Form_pg_type)GETSTRUCT(typ))->typbyval;
bool isnull;
ReleaseType(typ);
curtyp = exprType(node);
Assert(OidIsValid(curtyp));
if (curtyp != target_type && OidIsValid(target_type)) {
node = coerce_type(NULL, node, curtyp, target_type, typmod,
COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
if (!PointerIsValid(node))
elog(ERROR, "could not coerce partitioning parameter");
}
res = partition_arg_get_val(node, &isnull);
c = makeConst(target_type, typmod, typlen, res, isnull, typbyval);
return c;
}
}
/*
* Get the actual value from the expression. There are only a limited range
* of cases we must cover because the parser guarantees constant input.
*/
Datum partition_arg_get_val(Node *node, bool *isnull) {
switch (nodeTag(node)) {
case T_FuncExpr: {
/* must have been cast to the operator. */
FuncExpr *fe = (FuncExpr *)node;
Datum d = PointerGetDatum(NULL);
switch (list_length(fe->args)) {
case 1: {
Datum d1 = partition_arg_get_val(linitial(fe->args), isnull);
if (!*isnull) d = OidFunctionCall1(fe->funcid, d1);
} break;
case 2: {
bool null1, null2;
Datum d1 = partition_arg_get_val(linitial(fe->args), &null1);
Datum d2 = partition_arg_get_val(lsecond(fe->args), &null2);
*isnull = null1 || null2;
if (!*isnull) d = OidFunctionCall2(fe->funcid, d1, d2);
} break;
case 3: {
bool null1, null2, null3;
Datum d1 = partition_arg_get_val(linitial(fe->args), &null1);
Datum d2 = partition_arg_get_val(lsecond(fe->args), &null2);
Datum d3 = partition_arg_get_val(lthird(fe->args), &null3);
*isnull = null1 || null2 || null3;
if (!*isnull) d = OidFunctionCall3(fe->funcid, d1, d2, d3);
} break;
default:
elog(ERROR,
"unexpected number of coercion function "
"arguments: %d",
list_length(fe->args));
break;
}
return d;
} break;
case T_OpExpr: {
OpExpr *op = (OpExpr *)node;
Datum d = 0;
if (!OidIsValid(op->opfuncid)) op->opfuncid = get_opcode(op->opno);
switch (list_length(op->args)) {
case 1: {
Datum d1 = partition_arg_get_val(linitial(op->args), isnull);
if (!*isnull) d = OidFunctionCall1(op->opfuncid, d1);
} break;
case 2: {
bool null1, null2;
Datum d1 = partition_arg_get_val(linitial(op->args), &null1);
Datum d2 = partition_arg_get_val(lsecond(op->args), &null2);
*isnull = null1 || null2;
if (!*isnull) d = OidFunctionCall2(op->opfuncid, d1, d2);
} break;
default:
elog(ERROR,
"unexpected number of arguments for operator function: %d",
list_length(op->args));
}
return d;
}
*isnull = false;
break;
case T_RelabelType: {
RelabelType *n = (RelabelType *)node;
return partition_arg_get_val((Node *)n->arg, isnull);
} break;
case T_Const:
*isnull = ((Const *)node)->constisnull;
return ((Const *)node)->constvalue;
break;
case T_CoerceToDomain: {
CoerceToDomain *c = (CoerceToDomain *)node;
return partition_arg_get_val((Node *)c->arg, isnull);
} break;
default:
elog(ERROR, "unknown partitioning argument type: %d", nodeTag(node));
break;
}
return 0; /* quieten GCC */
}
/*
* Evaluate a basic operator expression from a partitioning specification.
* The expression will only be an op expr but the sides might contain
* a coercion function. The underlying value will be a simple constant,
* however.
*
* If restypid is non-NULL and *restypid is set to InvalidOid, we tell the
* caller what the return type of the operator is. If it is anything but
* InvalidOid, coerce the operation's result to that type.
*/
static Datum eval_basic_opexpr(ParseState *pstate, List *oprname, Node *leftarg,
Node *rightarg, bool *typbyval, int16 *typlen,
Oid *restypid, int location) {
Datum res = 0;
Datum lhs = 0;
Datum rhs = 0;
OpExpr *opexpr;
bool byval;
int16 len;
bool need_typ_info = (PointerIsValid(restypid) && *restypid == InvalidOid);
bool isnull;
Oid oprcode;
int fetchCount;
opexpr = (OpExpr *)make_op(pstate, oprname, leftarg, rightarg, location);
oprcode = caql_getoid_plus(NULL, &fetchCount, NULL,
cql("SELECT oprcode FROM pg_operator "
" WHERE oid = :1 ",
ObjectIdGetDatum(opexpr->opno)));
if (!fetchCount) /* should not fail */
elog(ERROR, "cache lookup failed for operator %u", opexpr->opno);
opexpr->opfuncid = oprcode;
lhs = partition_arg_get_val((Node *)linitial(opexpr->args), &isnull);
if (!isnull) {
rhs = partition_arg_get_val((Node *)lsecond(opexpr->args), &isnull);
if (!isnull) res = OidFunctionCall2(opexpr->opfuncid, lhs, rhs);
}
/* If the caller supplied a target result type, coerce if necesssary */
if (PointerIsValid(restypid)) {
if (OidIsValid(*restypid)) {
if (*restypid != opexpr->opresulttype) {
Expr *e;
Type typ = typeidType(opexpr->opresulttype);
int32 typmod;
Const *c;
c = makeConst(opexpr->opresulttype, -1, typeLen(typ), res, isnull,
typeByVal(typ));
ReleaseType(typ);
typ = typeidType(*restypid);
typmod = ((Form_pg_type)GETSTRUCT(typ))->typtypmod;
ReleaseType(typ);
e = (Expr *)coerce_type(NULL, (Node *)c, opexpr->opresulttype,
*restypid, typmod, COERCION_EXPLICIT,
COERCE_IMPLICIT_CAST, -1);
res = partition_arg_get_val((Node *)e, &isnull);
}
} else {
*restypid = opexpr->opresulttype;
}
} else {
return res;
}
if (need_typ_info || !PointerIsValid(typbyval)) {
Type typ = typeidType(*restypid);
byval = typeByVal(typ);
len = typeLen(typ);
if (PointerIsValid(typbyval)) {
Assert(PointerIsValid(typlen));
*typbyval = byval;
*typlen = len;
}
ReleaseType(typ);
} else {
byval = *typbyval;
len = *typlen;
}
res = datumCopy(res, byval, len);
return res;
}
/*
* XXX: this is awful, rewrite
*/
static int partition_range_every(ParseState *pstate, PartitionBy *pBy,
List *coltypes, char *at_depth,
partValidationState *vstate) {
PartitionSpec *pSpec;
char namBuf[NAMEDATALEN];
int numElts = 0;
List *partElts;
ListCell *lc = NULL;
ListCell *lc_prev = NULL;
List *stenc = NIL;
Assert(pBy);
Assert(pBy->partType == PARTTYP_RANGE);
pSpec = (PartitionSpec *)pBy->partSpec;
if (pSpec) /* no bound spec for default partition */
{
partElts = pSpec->partElem;
stenc = pSpec->enc_clauses;
lc = list_head(partElts);
}
for (; lc;) /* foreach */
{
PartitionElem *pElem = (PartitionElem *)lfirst(lc);
PartitionBoundSpec *pBSpec = NULL;
int numCols = 0;
int colCnt = 0;
Const *everyCnt;
PartitionRangeItem *pRI_Start = NULL;
PartitionRangeItem *pRI_End = NULL;
PartitionRangeItem *pRI_Every = NULL;
ListCell *lc_Start = NULL;
ListCell *lc_End = NULL;
ListCell *lc_Every = NULL;
Node *pStoreAttr = NULL;
List *allNewCols;
List *allNewPartns = NIL;
List *lastval = NIL;
bool do_every_param_test = true;
pBSpec = NULL;
if (pElem && IsA(pElem, ColumnReferenceStorageDirective)) {
stenc = lappend(stenc, pElem);
goto l_next_iteration;
}
numElts++;
if (!pElem)
goto l_EveryLoopEnd;
else {
pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
if (!pBSpec) goto l_EveryLoopEnd;
pStoreAttr = pElem->storeAttr;
if (pElem->partName) {
snprintf(namBuf, sizeof(namBuf), " \"%s\"", strVal(pElem->partName));
} else {
snprintf(namBuf, sizeof(namBuf), " number %d", numElts);
}
if (pElem->isDefault) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of boundary specification "
"for DEFAULT partition%s%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
} /* end if is default */
/* MPP-3541: invalid use of LIST spec */
if (!IsA(pBSpec, PartitionBoundSpec)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"invalid use of LIST boundary specification "
"in partition%s of type RANGE%s",
namBuf, at_depth),
errOmitLocation(true),
/* MPP-4249: use value spec location if have one */
((IsA(pBSpec, PartitionValuesSpec))
? parser_errposition(
pstate, ((PartitionValuesSpec *)pBSpec)->location)
:
/* else use invalid parsestate/postition */
parser_errposition(NULL, 0))));
}
Assert(IsA(pBSpec, PartitionBoundSpec));
}
/* have a valid range bound spec */
if (!pBSpec->partEvery) goto l_EveryLoopEnd;
/* valid EVERY needs a START and END */
if (!(pBSpec->partStart && pBSpec->partEnd)) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"EVERY clause in partition%s "
"requires START and END%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pBSpec->location)));
}
/* MPP-6297: check for WITH (tablename=name) clause
* [magic to make dump/restore work by ignoring EVERY]
*/
if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
ListCell *prev_lc = NULL;
ListCell *def_lc = NULL;
List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
bool bTablename = false;
foreach (def_lc, pWithList) {
DefElem *pDef = (DefElem *)lfirst(def_lc);
/* get the tablename from the WITH, then remove this
* element from the list */
if (0 == strcmp(pDef->defname, "tablename")) {
/* if the string isn't quoted you get a typename ? */
if (!IsA(pDef->arg, String))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid tablename specification")));
bTablename = true;
bool need_free_value = false;
char *widthname_str = defGetString(pDef, &need_free_value);
pBSpec->pWithTnameStr = pstrdup(widthname_str);
if (need_free_value) {
pfree(widthname_str);
widthname_str = NULL;
}
AssertImply(need_free_value, NULL == widthname_str);
pWithList = list_delete_cell(pWithList, def_lc, prev_lc);
((AlterPartitionCmd *)pStoreAttr)->arg1 = (Node *)pWithList;
break;
}
prev_lc = def_lc;
} /* end foreach */
if (bTablename) goto l_EveryLoopEnd;
}
everyCnt = make_const(pstate, makeInteger(1), -1);
pRI_Start = (PartitionRangeItem *)pBSpec->partStart;
pRI_End = (PartitionRangeItem *)pBSpec->partEnd;
pRI_Every = (PartitionRangeItem *)pBSpec->partEvery;
numCols = list_length(pRI_Every->partRangeVal);
if ((numCols != list_length(pRI_Start->partRangeVal)) ||
(numCols != list_length(pRI_End->partRangeVal))) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"mismatch between EVERY, START and END "
"in partition%s%s",
namBuf, at_depth),
errOmitLocation(true),
parser_errposition(pstate, pBSpec->location)));
}
/* XXX XXX: need to save prev value of
every_num * (every_cnt * n3t) for first column only
*/
for (;;) /* loop until exceed end */
{
ListCell *coltype = list_head(coltypes);
ListCell *lclastval = list_head(lastval);
List *curval = NIL;
int sqlRc = 0;
lc_Start = list_head(pRI_Start->partRangeVal);
lc_End = list_head(pRI_End->partRangeVal);
lc_Every = list_head(pRI_Every->partRangeVal);
colCnt = numCols;
allNewCols = NIL;
for (; lc_Start && lc_End && lc_Every;) /* for all cols */
{
Node *n1 = lfirst(lc_Start);
Node *n2 = lfirst(lc_End);
Node *n3 = lfirst(lc_Every);
TypeName *type = lfirst(coltype);
char *compare_op = (1 == colCnt) ? "<" : "<=";
Node *n1t, *n2t, *n3t;
Datum res;
Const *c;
Const *newend;
List *oprmul, *oprplus, *oprcompare, *ltop;
Oid restypid;
Type typ;
char *outputstr;
Oid coltypid = type->typid;
if (!OidIsValid(coltypid)) {
Type t = typenameType(pstate, type);
coltypid = type->typid = typeTypeId(t);
ReleaseType(t);
}
oprmul = lappend(NIL, makeString("*"));
oprplus = lappend(NIL, makeString("+"));
oprcompare = lappend(NIL, makeString(compare_op));
ltop = lappend(NIL, makeString("<"));
n1t = transformExpr(pstate, n1);
n1t = coerce_type(NULL, n1t, exprType(n1t), coltypid, type->typmod,
COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
n1t = (Node *)flatten_partition_val(n1t, coltypid);
n2t = transformExpr(pstate, n2);
n2t = coerce_type(NULL, n2t, exprType(n2t), coltypid, type->typmod,
COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, -1);
n2t = (Node *)flatten_partition_val(n2t, coltypid);
n3t = transformExpr(pstate, n3);
n3t = (Node *)flatten_partition_val(n3t, exprType(n3t));
Assert(IsA(n3t, Const));
Assert(IsA(n2t, Const));
Assert(IsA(n1t, Const));
/* formula is n1t + (every_cnt * every_num * n3t) [<|<=] n2t */
/* every_cnt * n3t */
restypid = InvalidOid;
res = eval_basic_opexpr(pstate, oprmul, (Node *)everyCnt, n3t, NULL,
NULL, &restypid, ((A_Const *)n3)->location);
typ = typeidType(restypid);
c = makeConst(restypid, -1, typeLen(typ), res, false, typeByVal(typ));
ReleaseType(typ);
/*
* n1t + (...)
* NOTE: order is important because several useful operators,
* like date + interval, only have a built in function when
* the order is this way (the reverse is implemented using
* an SQL function which we cannot evaluate at the moment).
*/
restypid = exprType(n1t);
res = eval_basic_opexpr(pstate, oprplus, n1t, (Node *)c, NULL, NULL,
&restypid, ((A_Const *)n1)->location);
typ = typeidType(restypid);
newend =
makeConst(restypid, -1, typeLen(typ), res, false, typeByVal(typ));
ReleaseType(typ);
/*
* Now we must detect a few conditions.
*
* We must reject every() parameters which do not significantly
* increment the starting value. For example:
*
* 1: start ('2008-01-01') end ('2010-01-01') every('0 days')
*
* We'll detect this case on the second iteration by observing
* that the current partition end is no greater than the
* previous.
*
* We must also consider:
*
* 2: start (1) end (10) every(0.5)
* 3: start (1) end (10) every(1.5)
*
* The problem here is that 1.5 and 2.5 will be rounded to 2 and
* 3 respectively. This means they look sane to the code but
* they aren't. So, we must test that:
*
* cast(1 + 0.5 as int) != (1 + 0.5)::numeric
*
* If we make it past this point, we still might see a current
* value less than the previous value. That would be caused by
* an overflow of the type which is undetected by the type
* itself. Most types detect overflow but time and timetz do
* not. So, if we're beyond the second iteration and detect and
* overflow, error out.
*/
/* do it on the first iteration */
if (!lclastval) {
List *opreq = lappend(NIL, makeString("="));
Datum uncast;
Datum iseq;
Const *tmpconst;
Oid test_typid = InvalidOid;
uncast =
eval_basic_opexpr(pstate, oprplus, n1t, (Node *)c, NULL, NULL,
&test_typid, ((A_Const *)n1)->location);
typ = typeidType(test_typid);
tmpconst = makeConst(test_typid, -1, typeLen(typ), uncast, false,
typeByVal(typ));
ReleaseType(typ);
iseq = eval_basic_opexpr(NULL, opreq, (Node *)tmpconst,
(Node *)newend, NULL, NULL, NULL, -1);
if (!DatumGetBool(iseq))
ereport(
ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("EVERY parameter produces ambiguous partition rule"),
errOmitLocation(true),
parser_errposition(pstate, ((A_Const *)n3)->location)));
}
if (lclastval) {
Datum res2;
Oid tmptyp = InvalidOid;
/*
* now test for case 2 and 3 above: ensure that
* the cast value is equal to the uncast value.
*/
res2 = eval_basic_opexpr(pstate, ltop, (Node *)lfirst(lclastval),
(Node *)newend, NULL, NULL, &tmptyp,
((A_Const *)n3)->location);
if (!DatumGetBool(res2)) {
/*
* Second iteration: parameter hasn't increased the
* current end from the old end.
*/
if (do_every_param_test) {
ereport(
ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("EVERY parameter too small"), errOmitLocation(true),
parser_errposition(pstate, ((A_Const *)n3)->location)));
} else {
/*
* We got a smaller value but later than we thought
* so it must be an overflow.
*/
ereport(
ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("END parameter not reached before type overflows"),
errOmitLocation(true),
parser_errposition(pstate, ((A_Const *)n2)->location)));
}
}
}
curval = lappend(curval, newend);
/* get the string for the calculated type */
{
Oid ooutput;
bool isvarlena;
FmgrInfo finfo;
getTypeOutputInfo(restypid, &ooutput, &isvarlena);
fmgr_info(ooutput, &finfo);
if (isvarlena) res = PointerGetDatum(PG_DETOAST_DATUM(res));
outputstr = OutputFunctionCall(&finfo, res);
}
/* the comparison */
restypid = InvalidOid;
res = eval_basic_opexpr(pstate, oprcompare, (Node *)newend, n2t, NULL,
NULL, &restypid, ((A_Const *)n2)->location);
/* XXX XXX: also check stop flag.
and free up prev if stop is true or current > end.
*/
if (!DatumGetBool(res)) {
if (outputstr) pfree(outputstr);
sqlRc = 0;
break;
} else {
allNewCols = lappend(allNewCols, pstrdup(outputstr));
if (outputstr) pfree(outputstr);
sqlRc = 1;
}
lc_Start = lnext(lc_Start);
lc_End = lnext(lc_End);
lc_Every = lnext(lc_Every);
coltype = lnext(coltype);
if (lclastval) lclastval = lnext(lclastval);
colCnt--;
} /* end for all cols */
if (lc_Start || lc_End ||
lc_Every) { /* allNewCols is incomplete - don't use */
if (allNewCols) list_free_deep(allNewCols);
} else {
allNewPartns = lappend(allNewPartns, allNewCols);
}
if (!sqlRc) break;
everyCnt->constvalue++;
/* if we have lastval set, the every test will have been done */
if (lastval) do_every_param_test = false;
lastval = curval;
} /* end loop until exceed end */
l_EveryLoopEnd:
if (allNewPartns) Assert(pBSpec); /* check for default partitions... */
if (pBSpec) pBSpec->everyGenList = allNewPartns;
l_next_iteration:
lc_prev = lc;
lc = lnext(lc);
} /* end foreach */
pSpec->enc_clauses = stenc;
return 1;
} /* end partition_range_every */
/*
* Add or override any column reference storage directives inherited from the
* parent table.
*/
static void merge_part_column_encodings(CreateStmtBase *cs, List *stenc) {
ListCell *lc;
List *parentencs = NIL;
List *others = NIL;
List *finalencs = NIL;
/* Don't waste time unnecessarily */
if (!stenc) return;
/*
* First, split the table elements into column reference storage directives
* and everything else.
*/
foreach (lc, cs->tableElts) {
Node *n = lfirst(lc);
if (IsA(n, ColumnReferenceStorageDirective))
parentencs = lappend(parentencs, n);
else
others = lappend(others, n);
}
/*
* Now build a final set of storage directives for this partition. Start
* with stenc and then add everything from parentencs which doesn't appear
* in that. This means we prefer partition level directives over those
* directives specified for the parent.
*
* We must copy stenc, since list_concat may modify it destructively.
*/
finalencs = list_copy(stenc);
foreach (lc, parentencs) {
bool found = false;
ListCell *lc2;
ColumnReferenceStorageDirective *p = lfirst(lc);
if (p->deflt) continue;
foreach (lc2, finalencs) {
ColumnReferenceStorageDirective *f = lfirst(lc2);
if (f->deflt) continue;
if (equal(f->column, p->column)) {
found = true;
break;
}
}
if (!found) finalencs = lappend(finalencs, p);
}
/*
* Finally, make sure we don't propagate any conflicting clauses in the
* ColumnDef.
*/
foreach (lc, others) {
Node *n = lfirst(lc);
if (IsA(n, ColumnDef)) {
ColumnDef *c = (ColumnDef *)n;
ListCell *lc2;
foreach (lc2, finalencs) {
ColumnReferenceStorageDirective *r = lfirst(lc2);
if (r->deflt) continue;
if (strcmp(strVal(r->column), c->colname) == 0) {
c->encoding = NIL;
break;
}
}
}
}
cs->tableElts = list_concat(others, finalencs);
}
static void make_child_node(CreateStmtBase *stmt, CreateStmtContext *cxt,
char *relname, PartitionBy *curPby, Node *newSub,
Node *pRuleCatalog, Node *pPostCreate,
Node *pConstraint, Node *pStoreAttr, char *prtstr,
bool bQuiet, List *stenc) {
RangeVar *parent_tab_name = makeNode(RangeVar);
RangeVar *child_tab_name = makeNode(RangeVar);
CreateStmtBase child_tab_stmt;
memset(&child_tab_stmt, 0, sizeof(CreateStmtBase));
parent_tab_name->catalogname = cxt->relation->catalogname;
parent_tab_name->schemaname = cxt->relation->schemaname;
parent_tab_name->relname = cxt->relation->relname;
parent_tab_name->location = -1;
child_tab_name->catalogname = cxt->relation->catalogname;
child_tab_name->schemaname = cxt->relation->schemaname;
child_tab_name->relname = relname;
child_tab_name->location = -1;
child_tab_stmt.relation = child_tab_name;
child_tab_stmt.is_part_child = true;
child_tab_stmt.is_add_part = stmt->is_add_part;
if (!bQuiet)
ereport(NOTICE, (errmsg(
"%s will create partition \"%s\" for "
"table \"%s\"",
cxt->stmtType, child_tab_name->relname,
cxt->relation->relname)));
/* set the "Post Create" rule if it exists */
child_tab_stmt.postCreate = pPostCreate;
/*
* Deep copy the parent's table elements.
*
* XXX The copy may be unnecessary, but it is safe.
*
* Previously, some following code updated constraint names
* in the tableElts to assure uniqueness and handle issues
* with FKs (?). This required a copy.
*
* However, forcing a name change at this level overrides any
* user-specified constraint names, so we don't do one here
* any more.
*/
child_tab_stmt.tableElts = copyObject(stmt->tableElts);
merge_part_column_encodings(&child_tab_stmt, stenc);
/* Hash partitioning special case. */
if (pConstraint &&
((enable_partition_rules && curPby->partType == PARTTYP_HASH) ||
curPby->partType != PARTTYP_HASH))
child_tab_stmt.tableElts = lappend(child_tab_stmt.tableElts, pConstraint);
/*
* XXX XXX: inheriting the parent causes a headache in
* transformDistributedBy, since it assumes the parent exists
* already. Just add an "ALTER TABLE...INHERIT parent" after
* the create child table
*/
/*child_tab_stmt->inhRelations = list_make1(parent_tab_name); */
child_tab_stmt.inhRelations = list_copy(stmt->inhRelations);
child_tab_stmt.constraints = copyObject(stmt->constraints);
child_tab_stmt.options = stmt->options;
/* allow WITH clause for appendonly tables */
if (pStoreAttr) {
AlterPartitionCmd *psa_apc = (AlterPartitionCmd *)pStoreAttr;
/* Options */
if (psa_apc->arg1) child_tab_stmt.options = (List *)psa_apc->arg1;
/* Tablespace from parent (input CreateStmt)... */
if (psa_apc->arg2 && *strVal(psa_apc->arg2))
child_tab_stmt.tablespacename = strVal(psa_apc->arg2);
}
/* ...or tablespace from root. */
if (!child_tab_stmt.tablespacename && stmt->tablespacename)
child_tab_stmt.tablespacename = stmt->tablespacename;
child_tab_stmt.oncommit = stmt->oncommit;
child_tab_stmt.distributedBy = stmt->distributedBy;
/* use the newSub as the partitionBy if the current
* partition elem had an inline subpartition declaration
*/
child_tab_stmt.partitionBy = (Node *)newSub;
child_tab_stmt.relKind = RELKIND_RELATION;
/*
* Adjust tablespace name for the CREATE TABLE via ADD PARTITION. (MPP-8047)
*
* The way we traverse the hierarchy, parents are visited before children, so
* we can usually pick up tablespace from the parent relation. If the child
* is a top-level branch, though, we take the tablespace from the root.
* Ultimately, we take the tablespace as specified in the command, or, if none
* was specified, the one from the root paritioned table.
*/
if (!child_tab_stmt.tablespacename) {
Oid poid = RangeVarGetRelid(cxt->relation, true,
false /*allowHcatalog*/); /* parent branch */
if (!poid) {
poid = RangeVarGetRelid(
stmt->relation, true,
false /*alloweHcatalog*/); /* whole partitioned table */
}
if (poid) {
Relation prel = RelationIdGetRelation(poid);
child_tab_stmt.tablespacename =
get_tablespace_name(prel->rd_rel->reltablespace);
RelationClose(prel);
}
}
bool isChildTableExt = false;
if (pStoreAttr) {
if (((AlterPartitionCmd *)pStoreAttr)->format)
isChildTableExt = true;
else
isChildTableExt = false;
} else {
isChildTableExt = cxt->isExternalTable;
}
if (isChildTableExt) {
CreateExternalStmt *childStmt = makeNode(CreateExternalStmt);
childStmt->base = child_tab_stmt;
if (pStoreAttr) {
childStmt->format = pstrdup(((AlterPartitionCmd *)pStoreAttr)->format);
childStmt->base.options = ((AlterPartitionCmd *)pStoreAttr)->arg1;
childStmt->iswritable = true;
childStmt->exttypedesc = makeNode(ExtTableTypeDesc);
} else {
childStmt->exttypedesc = copyObject(cxt->exttypedesc);
childStmt->format = pstrdup(cxt->format);
childStmt->iswritable = cxt->iswritable;
}
// compatible with hive dir for partition table
if (cxt->isExternalTable == false || cxt->parentPath == NULL)
childStmt->parentPath = pstrdup(parent_tab_name->relname);
else {
char *path = (char *)palloc(sizeof(char) *
(strlen(cxt->parentPath)
+ strlen(parent_tab_name->relname)
+ 1 +1));
sprintf(path, "%s/%s",cxt->parentPath, parent_tab_name->relname);
childStmt->parentPath = pstrdup(path);
pfree(path);
}
cxt->alist = lappend(cxt->alist, childStmt);
} else {
CreateStmt *childStmt = makeNode(CreateStmt);
childStmt->base = child_tab_stmt;
cxt->alist = lappend(cxt->alist, childStmt);
}
/*
* ALTER TABLE for inheritance after CREATE TABLE ...
* XXX: Think of a better way.
*/
if (1) {
AlterTableCmd *atc = makeNode(AlterTableCmd);
AlterTableStmt *ats = makeNode(AlterTableStmt);
InheritPartitionCmd *ipc = makeNode(InheritPartitionCmd);
if (isChildTableExt) {
ats->relkind = OBJECT_EXTTABLE;
ats->greenWay = true;
} else {
ats->relkind = OBJECT_TABLE;
ats->greenWay = true;
}
/* alter table child inherits parent */
atc->subtype = AT_AddInherit;
ipc->parent = parent_tab_name;
atc->def = (Node *)ipc;
ats->relation = child_tab_name;
ats->cmds = list_make1((Node *)atc);
/* this is the deepest we're going, add the partition rules */
if (1) {
AlterTableCmd *atc2 = makeNode(AlterTableCmd);
/* alter table add child to partition set */
atc2->subtype = AT_PartAddInternal;
atc2->def = (Node *)curPby;
ats->cmds = lappend(ats->cmds, atc2);
}
cxt->alist = lappend(cxt->alist, ats);
}
}
static void expand_hash_partition_spec(PartitionBy *pBy) {
PartitionSpec *spec;
long i;
long max;
List *elem = NIL;
A_Const *con;
Assert(pBy->partType == PARTTYP_HASH);
Assert(pBy->partSpec == NULL);
spec = makeNode(PartitionSpec);
con = (A_Const *)pBy->partNum;
Assert(IsA(&con->val, Integer));
max = intVal(&con->val);
for (i = 0; i < max; i++) {
PartitionElem *el = makeNode(PartitionElem);
elem = lappend(elem, el);
}
spec->partElem = elem;
pBy->partSpec = (Node *)spec;
}
static bool partition_col_walker(Node *node, void *context) {
if (node == NULL) return false;
if (IsA(node, PartitionSpec)) {
PartitionSpec *s = (PartitionSpec *)node;
if (partition_col_walker(s->subSpec, context)) return true;
return partition_col_walker((Node *)s->partElem, context);
} else if (IsA(node, PartitionElem)) {
PartitionElem *el = (PartitionElem *)node;
return partition_col_walker(el->subSpec, context);
} else if (IsA(node, PartitionBy)) {
PartitionBy *p = (PartitionBy *)node;
ListCell *lc;
part_col_cxt *cxt = (part_col_cxt *)context;
foreach (lc, p->keys) {
char *colname = strVal(lfirst(lc));
ListCell *llc;
foreach (llc, cxt->cols) {
char *col = lfirst(llc);
if (strcmp(col, colname) == 0)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"column \"%s\" specified in multiple "
"partitioning keys",
colname),
errOmitLocation(true),
parser_errposition(cxt->pstate, p->location)));
}
cxt->cols = lappend(cxt->cols, colname);
}
if (partition_col_walker(p->subPart, context)) return true;
return partition_col_walker(p->partSpec, context);
}
return expression_tree_walker(node, partition_col_walker, context);
}
/*
* transformPartitionBy() - transform a partitioning clause attached to a CREATE
* TABLE statement.
*
* An example clause:
*
* PARTITION BY col1
* SUBPTN BY col2 SUBTEMPLATE (Spec2),
* SUBPTN BY col3 SUBTEMPLATE (Spec3)
* (Spec1)
*
* becomes a chain of PartitionBy structs, each with a PartitionSpec:
*
* pBy -> (col1, spec1, NULL subspec)
* |
* v
* pBy -> (col2, spec2, NULL subspec)
* |
* v
* pBy -> (col3, spec3, NULL subspec)
*
* This struct is easy to process recursively. However, for the syntax:
*
* PARTITION BY col1
* SUBPTN BY col2
* SUBPTN BY col3
* (
* PTN AA (SUB CCC (SUB EE, SUB FF), SUB DDD (SUB EE, SUB FF))
* PTN BB (SUB CCC (SUB EE, SUB FF), SUB DDD (SUB EE, SUB FF))
* )
*
* the struct is more like:
*
* pBy -> (col1, spec1, spec2->spec3)
* |
* v
* pBy -> (col2, NULL spec, NULL subspec)
* |
* v
* pBy -> (col3, NULL spec, NULL subspec)
*
*
* We need to move the subpartition specifications to the correct spots.
*
*/
static void transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt,
CreateStmtBase *stmt, Node *partitionBy,
GpPolicy *policy) {
Oid snamespaceid;
char *snamespace;
int partDepth; /* depth (starting at zero, but display at 1) */
char depthstr[NAMEDATALEN];
char *at_depth = "";
char at_buf[NAMEDATALEN];
int partNumber = -1;
int partno = 1;
int everyno = 0;
List *partElts = NIL;
ListCell *lc = NULL;
PartitionBy *pBy; /* the current partitioning clause */
PartitionBy *psubBy = NULL; /* child of current */
char prtstr[NAMEDATALEN];
Oid accessMethodId = InvalidOid;
bool lookup_opclass;
Node *prevEvery;
ListCell *lc_anp = NULL;
List *key_attnums = NIL;
List *key_attnames = NIL;
part_col_cxt pcolcxt;
List *stenc = NIL;
if (NULL == partitionBy) return;
pBy = (PartitionBy *)partitionBy;
/* disallow composite partition keys */
if (1 < list_length(pBy->keys)) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Composite partition keys are not allowed")));
}
partDepth = pBy->partDepth;
partDepth++; /* increment depth for subpartition */
if (0 < gp_max_partition_level && partDepth > gp_max_partition_level) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("Exceeded the maximum allowed level of partitioning of %d",
gp_max_partition_level)));
}
snprintf(depthstr, sizeof(depthstr), "%d", partDepth);
if (pBy->partDepth != 0) {
/*
* Only mention the depth 2 and greater to aid in debugging
* subpartitions
*/
snprintf(at_buf, sizeof(at_buf), " (at depth %d)", partDepth);
at_depth = at_buf;
} else
pBy->parentRel = copyObject(stmt->relation);
/* set the depth for the immediate subpartition */
if (pBy->subPart) {
psubBy = (PartitionBy *)pBy->subPart;
psubBy->partDepth = partDepth;
if (((PartitionBy *)pBy->subPart)->parentRel == NULL)
((PartitionBy *)pBy->subPart)->parentRel = copyObject(pBy->parentRel);
}
/*
* Derive the number of partitions from the PARTITIONS clause.
*/
if (pBy->partNum) {
A_Const *con = (A_Const *)pBy->partNum;
Assert(IsA(&con->val, Integer));
partNumber = intVal(&con->val);
if (pBy->partType != PARTTYP_HASH) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("%sPARTITIONS clause requires a HASH partition%s",
pBy->partDepth != 0 ? "SUB" : "", at_depth),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
}
if (partNumber < 1) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("%sPARTITIONS cannot be less than one%s",
pBy->partDepth != 0 ? "SUB" : "", at_depth),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
}
}
/*
* The recursive nature of this code means that if we're processing a
* sub partition rule, the opclass may have been looked up already.
*/
lookup_opclass = list_length(pBy->keyopclass) == 0;
if ((list_length(pBy->keys) > 1) && (pBy->partType == PARTTYP_RANGE)) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"too many columns for RANGE partition%s -- "
"only one column is allowed.",
at_depth),
errOmitLocation(true), parser_errposition(pstate, pBy->location)));
}
/* validate keys */
foreach (lc, pBy->keys) {
Value *colval = lfirst(lc);
char *colname = strVal(colval);
ListCell *columns;
bool found = false;
Oid typeid = InvalidOid;
Oid opclass = InvalidOid;
int i = 0;
foreach (columns, cxt->columns) {
ColumnDef *column = (ColumnDef *)lfirst(columns);
Assert(IsA(column, ColumnDef));
i++;
if (strcmp(column->colname, colname) == 0) {
found = true;
if (list_member_int(key_attnums, i))
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"column \"%s\" specified more than once "
"in partitioning key",
colname),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
key_attnums = lappend_int(key_attnums, i);
Insist(IsA(colval, String));
key_attnames = lappend(key_attnames, colval);
if (lookup_opclass) {
typeid = column->typname->typid;
if (!OidIsValid(typeid)) {
Type type = typenameType(pstate, column->typname);
column->typname->typid = typeid = typeTypeId(type);
ReleaseType(type);
}
}
break;
}
}
if (!found) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist in relation \"%s\"",
colname, cxt->relation->relname),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
}
if (lookup_opclass) {
/* get access method ID for this partition type */
switch (pBy->partType) {
case PARTTYP_HASH:
accessMethodId = HASH_AM_OID;
break;
case PARTTYP_RANGE:
case PARTTYP_LIST:
accessMethodId = BTREE_AM_OID;
break;
default:
elog(ERROR, "unknown partitioning type %i", pBy->partType);
break;
}
opclass = GetDefaultOpClass(typeid, accessMethodId);
if (!OidIsValid(opclass))
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("data type %s has no default operator class",
format_type_be(typeid)),
errOmitLocation(true)));
pBy->keyopclass = lappend_oid(pBy->keyopclass, opclass);
}
}
/* Have partitioning keys; check for violating unique constraints */
foreach (lc, cxt->ixconstraints) {
ListCell *ilc = NULL;
Constraint *ucon = (Constraint *)lfirst(lc);
Insist(ucon->keys != NIL);
foreach (ilc, key_attnames) {
Value *partkeyname = (Value *)lfirst(ilc);
if (!list_member(ucon->keys, partkeyname)) {
char *what = NULL;
switch (ucon->contype) {
case CONSTR_PRIMARY:
what = "PRIMARY KEY";
break;
case CONSTR_UNIQUE:
what = "UNIQUE";
break;
default:
elog(ERROR,
"unexpected constraint type in internal transformation");
break;
}
ereport(
ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg(
"%s constraint must contain all columns in the "
"partition key",
what),
errhint(
"Include column \"%s\" in the %s constraint or create "
"a part-wise UNIQUE index after creating the table instead.",
strVal(partkeyname), what)));
}
}
}
/* No further use for key_attnames, so clean up. */
if (key_attnames) {
list_free(key_attnames);
}
key_attnames = NIL;
/* see if there are any duplicate column references */
if (0) /* MPP-3988: allow same column in multiple partitioning
* keys at different levels */
partition_col_walker((Node *)pBy, &pcolcxt);
if (pBy->partType == PARTTYP_HASH) {
if (pBy->partSpec == NULL) {
if (pBy->partNum == NULL)
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"hash partition requires PARTITIONS clause "
"or partition specification"),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
/*
* Users don't have to specify a partition specification
* for HASH. If they didn't, create one so that the rest
* of the code can generate some valid partition children.
*/
expand_hash_partition_spec(pBy);
}
}
if (pBy->partSpec) {
partNumber =
validate_partition_spec(pstate, cxt, stmt, pBy, at_depth, partNumber);
stenc = ((PartitionSpec *)pBy->partSpec)->enc_clauses;
}
/*
* We'd better have some partitions to look at by now
*/
if (partNumber < 1) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("no partitions specified at depth %d", partDepth),
errOmitLocation(true), parser_errposition(pstate, pBy->location)));
}
/* Clear error position. */
pstate->p_breadcrumb.node = NULL;
/*
* Determine namespace and name to use for the child table.
*/
snamespaceid = RangeVarGetCreationNamespace(cxt->relation);
snamespace = get_namespace_name(snamespaceid);
/* set up the partition specification element list if it exists */
if (pBy->partSpec) {
PartitionSpec *pSpec = (PartitionSpec *)pBy->partSpec;
PartitionSpec *subSpec = (PartitionSpec *)pSpec->subSpec;
/*
* If the current specification has a subSpec, then that is
* the specification for the child, so we'd better have a
* SUBPARTITION BY clause for the child. The sub specification
* cannot contain a partition specification of its own.
*/
if (subSpec) {
if (NULL == psubBy) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"missing SUBPARTITION BY clause for "
"subpartition specification%s",
at_depth),
errOmitLocation(true),
parser_errposition(pstate, pBy->location)));
}
if (psubBy && psubBy->partSpec) {
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("subpartition specification conflict%s", at_depth),
errOmitLocation(true),
parser_errposition(pstate, psubBy->location)));
}
psubBy->partSpec = (Node *)subSpec;
}
partElts = pSpec->partElem;
}
Assert(partNumber > 0);
/*
* We must iterate through the elements in this way to support HASH
* partitioning, which likely has no partitioning elements.
*/
lc = list_head(partElts);
prevEvery = NULL;
/* Iterate through each partition element */
for (partno = 0; partno < partNumber; partno++) {
PartitionBy *newSub = NULL;
PartitionElem *pElem = lc ? (PartitionElem *)lfirst(lc) : NULL;
Node *pRuleCatalog = NULL;
Node *pPostCreate = NULL;
Node *pConstraint = NULL;
Node *pStoreAttr = NULL;
char *relname = NULL;
PartitionBoundSpec *pBSpec = NULL;
Node *every = NULL;
PartitionBy *curPby = NULL;
bool bQuiet = false;
char *pWithTname = NULL;
List *colencs = NIL;
/* silence partition name messages if really generating a
* subpartition template
*/
if (pBy->partQuiet == PART_VERBO_NOPARTNAME) bQuiet = true;
if (pElem) {
pStoreAttr = pElem->storeAttr;
colencs = pElem->colencs;
if (pElem->boundSpec && IsA(pElem->boundSpec, PartitionBoundSpec)) {
pBSpec = (PartitionBoundSpec *)pElem->boundSpec;
every = pBSpec->partEvery;
pWithTname = pBSpec->pWithTnameStr;
if (prevEvery && every) {
if (equal(prevEvery, every))
everyno++;
else {
everyno = 1; /* reset */
lc_anp = list_head(pBSpec->everyGenList);
}
} else if (every) {
everyno++;
if (!lc_anp) lc_anp = list_head(pBSpec->everyGenList);
} else {
everyno = 0;
lc_anp = NULL;
}
}
if (pElem->partName) {
/*
* Use the partition name as part of the child
* table name
*/
char *pName = strVal(pElem->partName);
snprintf(prtstr, sizeof(prtstr), "prt_%s", pName);
} else {
/*
* For the case where we don't have a partition name,
* don't worry about EVERY.
*/
if (pElem->rrand)
/* make a random name for ALTER TABLE ... ADD PARTITION */
snprintf(prtstr, sizeof(prtstr), "prt_r%lu",
pElem->rrand + partno + 1);
else
snprintf(prtstr, sizeof(prtstr), "prt_%d", partno + 1);
}
if (pElem->subSpec) {
/*
* If the current partition element has a subspec,
* then that is the spec for the child. So we'd
* better have a SUBPARTITION BY clause for the child,
* and that clause cannot contain a SUBPARTITION
* TEMPLATE (which is a spec). Note that we might
* have just updated the subBy partition spec with the
* subspec of the current spec, which would be a
* conflict. If the psubBy has a NULL spec, we make a
* copy of the node and update it -- the subSpec
* becomes the specification for the child. And if
* the subspec has a subspec it gets handled at the
* top of this loop as the subspec of the current spec
* when transformPartitionBy is re-invoked for the
* child table.
*/
if (NULL == psubBy) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"missing SUBPARTITION BY clause "
"for subpartition specification%s",
at_depth),
errOmitLocation(true),
parser_errposition(pstate, pElem->location)));
}
if (psubBy && psubBy->partSpec) {
Assert(((PartitionSpec *)psubBy->partSpec)->istemplate);
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"subpartition configuration conflicts "
"with subpartition template"),
errOmitLocation(true),
parser_errposition(pstate, psubBy->location)));
}
if (!((PartitionSpec *)pElem->subSpec)->istemplate &&
!gp_allow_non_uniform_partitioning_ddl) {
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"Multi-level partitioned tables without "
"templates are not supported")));
}
newSub = makeNode(PartitionBy);
newSub->partType = psubBy->partType;
newSub->keys = psubBy->keys;
newSub->partNum = psubBy->partNum;
newSub->subPart = psubBy->subPart;
newSub->partSpec = pElem->subSpec; /* use the subspec */
newSub->partDepth = psubBy->partDepth;
newSub->partQuiet = pBy->partQuiet;
if (pElem->subSpec) /* get a good location for error msg */
{
/* use subspec location */
newSub->location = ((PartitionSpec *)pElem->subSpec)->location;
} else {
newSub->location = pElem->location;
}
}
} else
snprintf(prtstr, sizeof(prtstr), "prt_%d", partno + 1);
/* MPP-6297: check for WITH (tablename=name) clause
* [only for dump/restore, set deep in the guts of
* partition_range_every...]
*/
if (pWithTname) {
relname = pWithTname;
prtstr[0] = '\0';
} else if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
/* MPP-6297, MPP-7661, MPP-7514: check for WITH
* (tablename=name) clause (AGAIN!) because the pWithTname
* is only set for an EVERY clause, and we might not have
* an EVERY clause.
*/
ListCell *prev_lc = NULL;
ListCell *def_lc = NULL;
List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
foreach (def_lc, pWithList) {
DefElem *pDef = (DefElem *)lfirst(def_lc);
/* get the tablename from the WITH, then remove this
* element from the list */
if (0 == strcmp(pDef->defname, "tablename")) {
/* if the string isn't quoted you get a typename ? */
if (!IsA(pDef->arg, String))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid tablename specification")));
bool need_free_value = false;
char *relname_str = defGetString(pDef, &need_free_value);
relname = pstrdup(relname_str);
if (need_free_value) {
pfree(relname_str);
relname_str = NULL;
}
AssertImply(need_free_value, NULL == relname_str);
prtstr[0] = '\0';
pWithList = list_delete_cell(pWithList, def_lc, prev_lc);
((AlterPartitionCmd *)pStoreAttr)->arg1 = (Node *)pWithList;
break;
}
prev_lc = def_lc;
} /* end foreach */
}
if (strlen(prtstr))
relname = ChooseRelationName(cxt->relation->relname, depthstr, /* depth */
prtstr, /* part spec */
snamespaceid, NULL);
/* check non-uniform bucketnum options */
if (pStoreAttr && ((AlterPartitionCmd *)pStoreAttr)->arg1) {
List *pWithList = (List *)(((AlterPartitionCmd *)pStoreAttr)->arg1);
int bucketnum = policy->bucketnum;
int child_bucketnum =
GetRelOpt_bucket_num_fromOptions(pWithList, bucketnum);
if (child_bucketnum != bucketnum)
ereport(ERROR, (errcode(ERRCODE_GP_FEATURE_NOT_SUPPORTED),
errmsg(
"distribution policy for \"%s\" "
"must be the same as that for \"%s\"",
relname, cxt->relation->relname)));
}
/* XXX: temporarily add rule creation code for debugging */
/* now that we have the child table name, make the rule */
if (!(pElem && pElem->isDefault)) {
ListCell *lc_rule = NULL;
int everycount = every ? ((PartitionRangeItem *)every)->everycount : 0;
List *allRules = make_partition_rules(
pstate, cxt, stmt, partitionBy, pElem, at_depth, relname, partno + 1,
partNumber, everyno, everycount, &lc_anp, true);
if (allRules) lc_rule = list_head(allRules);
if (lc_rule) {
pConstraint = lfirst(lc_rule);
if (pConstraint) {
StringInfoData sid;
Constraint *pCon = makeNode(Constraint);
initStringInfo(&sid);
appendStringInfo(&sid, "%s_%s", relname, "check");
pCon->contype = CONSTR_CHECK;
pCon->name = sid.data;
pCon->raw_expr = pConstraint;
pCon->cooked_expr = NULL;
pCon->indexspace = NULL;
pConstraint = (Node *)pCon;
}
lc_rule = lnext(lc_rule);
if (lc_rule) {
pRuleCatalog = lfirst(lc_rule);
/* look for a Rule statement to run after the
* relation is created
* (see DefinePartitionedRelation in tablecmds.c)
*/
lc_rule = lnext(lc_rule);
}
if (lc_rule) {
List *pL1 = NULL;
int *pInt1;
int *pInt2;
pInt1 = (int *)palloc(sizeof(int));
pInt2 = (int *)palloc(sizeof(int));
*pInt1 = partno + 1;
*pInt2 = everyno;
pL1 = list_make1(relname); /* rule name */
pL1 = lappend(pL1, pInt1); /* partition position */
pL1 = lappend(pL1, pInt2); /* every position */
if (pElem && pElem->partName) {
char *pName = strVal(pElem->partName);
pL1 = lappend(pL1, pName);
} else
pL1 = lappend(pL1, NULL);
pL1 = lappend(pL1, relname); /* child name */
pL1 = lappend(pL1, cxt->relation->relname); /* parent name */
if (enable_partition_rules)
pPostCreate = (Node *)list_make2(lfirst(lc_rule), pL1);
else
pPostCreate = NULL;
}
}
}
curPby = makeNode(PartitionBy);
{
PartitionSpec *tmppspec = makeNode(PartitionSpec);
/* selectively copy pBy */
curPby->partType = pBy->partType;
curPby->keys = key_attnums;
curPby->keyopclass = copyObject(pBy->keyopclass);
curPby->partNum = copyObject(pBy->partNum);
if (pElem) {
PartitionSpec *_spec = (PartitionSpec *)pBy->partSpec;
tmppspec->partElem = list_make1(copyObject(pElem));
tmppspec->istemplate = _spec->istemplate;
tmppspec->enc_clauses = copyObject(stenc);
}
curPby->partSpec = (Node *)tmppspec;
curPby->partDepth = pBy->partDepth;
curPby->partQuiet = pBy->partQuiet;
curPby->parentRel = copyObject(pBy->parentRel);
}
if (!newSub) newSub = copyObject(psubBy);
if (newSub) newSub->parentRel = copyObject(pBy->parentRel);
make_child_node(stmt, cxt, relname, curPby, (Node *)newSub, pRuleCatalog,
pPostCreate, pConstraint, pStoreAttr, prtstr, bQuiet,
colencs);
if (pBSpec) prevEvery = pBSpec->partEvery;
if (lc) lc = lnext(lc);
}
/* nefarious: we need to keep the "top"
* partition by statement because
* analyze.c:do_parse_analyze needs to find
* it to re-order the ALTER statements.
* (see cdbpartition.c:atpxPart_validate_spec)
*/
if ((pBy->partDepth > 0) && (pBy->bKeepMe != true)) {
/* we don't need this any more */
stmt->partitionBy = NULL;
stmt->is_part_child = true;
}
} /* end transformPartitionBy */
static void transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
bool skipValidation, bool isAddConstraint) {
ListCell *fkclist;
if (cxt->fkconstraints == NIL) return;
/*
* If CREATE TABLE or adding a column with NULL default, we can safely
* skip validation of the constraint.
*/
if (skipValidation) {
foreach (fkclist, cxt->fkconstraints) {
FkConstraint *fkconstraint = (FkConstraint *)lfirst(fkclist);
fkconstraint->skip_validation = true;
}
}
/*
* For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD
* CONSTRAINT command to execute after the basic command is complete. (If
* called from ADD CONSTRAINT, that routine will add the FK constraints to
* its own subcommand list.)
*
* Note: the ADD CONSTRAINT command must also execute after any index
* creation commands. Thus, this should run after
* transformIndexConstraints, so that the CREATE INDEX commands are
* already in cxt->alist.
*/
if (!isAddConstraint) {
AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
alterstmt->relation = cxt->relation;
alterstmt->cmds = NIL;
alterstmt->relkind = OBJECT_TABLE;
foreach (fkclist, cxt->fkconstraints) {
FkConstraint *fkconstraint = (FkConstraint *)lfirst(fkclist);
AlterTableCmd *altercmd = makeNode(AlterTableCmd);
altercmd->subtype = AT_ProcessedConstraint;
altercmd->name = NULL;
altercmd->def = (Node *)fkconstraint;
alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
}
cxt->alist = lappend(cxt->alist, alterstmt);
}
}
/*
* transformIndexStmt -
* transforms the qualification of the index statement
*
* If do_part is true, build create index statements for our children.
*/
static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt,
List **extras_before, List **extras_after) {
Query *qry;
RangeTblEntry *rte = NULL;
ListCell *l;
Oid idxOid;
Oid nspOid;
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
char *graph = stmt->relation->schemaname ? pstrdup(stmt->relation->schemaname) : NULL;
char *ele = stmt->relation->relname;
int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
if(isGraph) {
stmt->graphele = makeRangeVar(stmt->relation->schemaname, graph, ele, -1);
Oid dboid = GetCatalogId(stmt->relation->catalogname);
Oid namespaceId = LookupNamespaceId(stmt->relation->schemaname, dboid);
if(isGraph == 2 /* as edge*/) {
Oid relid = caql_getoid(
NULL,
cql("SELECT oid FROM pg_class "
" WHERE relname = :1 "
" AND relnamespace = :2 ",
CStringGetDatum(stmt->relation->relname),
ObjectIdGetDatum(namespaceId)));
Relation attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
cqContext cqc;
int colNum = caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM pg_attribute "
" WHERE attrelid = :1 AND attnum > :2",
ObjectIdGetDatum(relid), Int32GetDatum(0)));
cqContext *pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), attrelation),
cql("SELECT * FROM pg_attribute "
" WHERE attrelid = :1 AND attnum > :2",
ObjectIdGetDatum(relid), Int32GetDatum(0)));
HeapTuple attributeTuple;
char **colnames = palloc0(colNum * sizeof(char*));
while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
colnames[att->attnum - 1] = pstrdup(NameStr(att->attname));
}
caql_endscan(pcqCtx);
heap_close(attrelation, RowExclusiveLock);
Relation ElabelRelation = heap_open(ElabelRelationId, RowExclusiveLock);
HeapTuple elabelTuple= caql_getfirst(caql_addrel(cqclr(&cqc), ElabelRelation),
cql("SELECT * FROM skylon_elabel"
" WHERE elabelname = :1 AND schemaname = :2",
CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname)));
Form_skylon_elabel elabel = (Form_skylon_elabel) GETSTRUCT(elabelTuple);
Relation VlabelAttRelation = heap_open(VlabelAttrRelationId, RowExclusiveLock);
int srcNum =
caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
" WHERE vlabelname = :1 AND schemaname = :2 "
"AND primaryrank > :3",
CStringGetDatum(elabel->fromvlabel.data), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
int dstNum = caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
" WHERE vlabelname = :1 AND schemaname = :2 "
"AND primaryrank > :3",
CStringGetDatum(elabel->tovlabel.data), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
int primaryNum = srcNum + dstNum;
heap_close(VlabelAttRelation, RowExclusiveLock);
heap_close(ElabelRelation, RowExclusiveLock);
Relation elabelAttRelation = heap_open(ElabelAttrRelationId, RowExclusiveLock);
int eattnum = caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM skylon_elabel_attribute "
" WHERE elabelname = :1 AND schemaname = :2 "
"AND rank > :3",
CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
char **attnames = palloc0(eattnum * sizeof(char*));
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), elabelAttRelation),
cql("SELECT * FROM skylon_elabel_attribute "
" WHERE elabelname = :1 AND schemaname = :2 "
"AND rank > :3",
CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
Form_skylon_elabel_attribute att = (Form_skylon_elabel_attribute) GETSTRUCT(attributeTuple);
attnames[att->rank - 1] = pstrdup(NameStr(att->attrname));
}
caql_endscan(pcqCtx);
heap_close(elabelAttRelation, RowExclusiveLock);
ListCell *cell;
foreach(cell, stmt->indexParams) {
IndexElem *ele = (IndexElem *)lfirst(cell);
for(int i = 0; i < eattnum; i++) {
if(strcmp(attnames[i], ele->name) == 0) {
Value *attnum = makeInteger(0);
attnum->val.ival = i + 1;
stmt->graphIndexAttnum = lappend(stmt->graphIndexAttnum, attnum);
break;
}
}
}
foreach(cell, stmt->indexIncludingParams) {
IndexElem *ele = (IndexElem *)lfirst(cell);
for(int i = 0; i < eattnum; i++) {
if(strcmp(attnames[i], ele->name) == 0) {
Value *attnum = makeInteger(0);
attnum->val.ival = i + 1;
stmt->graphIncludeAttnum = lappend(stmt->graphIncludeAttnum, attnum);
break;
}
}
}
List *newIndexParams = NIL;
if(!stmt->reverse) {
for(int i = 0; i < primaryNum; i++) {
IndexElem *indexele = makeNode(IndexElem);
indexele->name = colnames[i];
newIndexParams = lappend(newIndexParams, indexele);
}
}
else {
for(int i = 0; i < dstNum; i++) {
IndexElem *indexele = makeNode(IndexElem);
indexele->name = colnames[srcNum + i];
newIndexParams = lappend(newIndexParams, indexele);
}
for(int i = 0; i < srcNum; i++) {
IndexElem *indexele = makeNode(IndexElem);
indexele->name = colnames[i];
newIndexParams = lappend(newIndexParams, indexele);
}
}
stmt->indexParams = list_concat(newIndexParams, stmt->indexParams);
}
else {
Relation vlabelAttRelation = heap_open(VlabelAttrRelationId, RowExclusiveLock);
cqContext cqc;
int vattnum = caql_getcount(
NULL,
cql("SELECT COUNT(*) FROM skylon_vlabel_attribute "
" WHERE vlabelname = :1 AND schemaname = :2 "
"AND rank > :3",
CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
char **attnames = palloc0(vattnum * sizeof(char*));
cqContext *pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), vlabelAttRelation),
cql("SELECT * FROM skylon_vlabel_attribute "
" WHERE vlabelname = :1 AND schemaname = :2 "
"AND rank > :3",
CStringGetDatum(ele), CStringGetDatum(stmt->relation->schemaname), Int32GetDatum(0)));
HeapTuple attributeTuple;
while (HeapTupleIsValid(attributeTuple = caql_getnext(pcqCtx))) {
Form_skylon_vlabel_attribute att = (Form_skylon_vlabel_attribute) GETSTRUCT(attributeTuple);
attnames[att->rank - 1] = pstrdup(NameStr(att->attrname));
}
caql_endscan(pcqCtx);
heap_close(vlabelAttRelation, RowExclusiveLock);
ListCell *cell;
foreach(cell, stmt->indexParams) {
IndexElem *ele = (IndexElem *)lfirst(cell);
for(int i = 0; i < vattnum; i++) {
if(strcmp(attnames[i], ele->name) == 0) {
Value *attnum = makeInteger(0);
attnum->val.ival = i + 1;
stmt->graphIndexAttnum = lappend(stmt->graphIndexAttnum, attnum);
break;
}
}
}
foreach(cell, stmt->indexIncludingParams) {
IndexElem *ele = (IndexElem *)lfirst(cell);
for(int i = 0; i < vattnum; i++) {
if(strcmp(attnames[i], ele->name) == 0) {
Value *attnum = makeInteger(0);
attnum->val.ival = i + 1;
stmt->graphIncludeAttnum = lappend(stmt->graphIncludeAttnum, attnum);
break;
}
}
}
}
}
/*
* If the table already exists (i.e., this isn't a create table time
* expansion of primary key() or unique()) and we're the ultimate parent
* of a partitioned table, cascade to all children. We don't do this
* at create table time because transformPartitionBy() automatically
* creates the indexes on the child tables for us.
*
* If this is a CREATE INDEX statement, idxname should already exist.
*/
idxOid = RangeVarGetRelid(stmt->relation, true, false /*allowHcatalog*/);
nspOid = RangeVarGetCreationNamespace(stmt->relation);
if (OidIsValid(idxOid) && stmt->idxname) {
Relation rel;
PG_TRY();
{ rel = heap_openrv(stmt->relation, AccessShareLock); }
PG_CATCH();
{
/*
* In the case of the table being dropped concurrently,
* throw a friendlier error than:
*
* "could not open relation with relid 1234"
*/
if (stmt->relation->schemaname)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s.%s\" does not exist",
stmt->relation->schemaname, stmt->relation->relname),
errOmitLocation(true)));
else
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" does not exist",
stmt->relation->relname),
errOmitLocation(true)));
PG_RE_THROW();
}
PG_END_TRY();
if (RelationBuildPartitionDesc(rel, false)) stmt->do_part = true;
/* native orc can't create index in parent relation */
if (RelationIsOrc(rel) && stmt->do_part)
ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("Cannot support create index statement in native orc parent relation yet")));
if (stmt->do_part && Gp_role != GP_ROLE_EXECUTE) {
List *children;
struct HTAB *nameCache;
/* Lookup the parser object name cache */
nameCache = parser_get_namecache(pstate);
/* Loop over all partition children */
children = find_inheritance_children(RelationGetRelid(rel));
foreach (l, children) {
Oid relid = lfirst_oid(l);
Relation crel = heap_open(
relid,
NoLock); /* lock on master
is enough */
IndexStmt *chidx;
Relation partrel;
HeapTuple tuple;
cqContext cqc;
char *parname;
int2 position;
int4 depth;
NameData name;
Oid paroid;
char depthstr[NAMEDATALEN];
char prtstr[NAMEDATALEN];
chidx = (IndexStmt *)copyObject((Node *)stmt);
/* now just update the relation and index name fields */
chidx->relation =
makeRangeVar(NULL /*catalogname*/,
get_namespace_name(RelationGetNamespace(crel)),
pstrdup(RelationGetRelationName(crel)), -1);
elog(NOTICE, "building index for child partition \"%s\"",
RelationGetRelationName(crel));
/*
* We want the index name to resemble our partition table name
* with the master index name on the front. This means, we
* append to the indexname the parname, position, and depth
* as we do in transformPartitionBy().
*
* So, firstly we must retrieve from pg_partition_rule the
* partition descriptor for the current relid. This gives us
* partition name and position. With paroid, we can get the
* partition level descriptor from pg_partition and therefore
* our depth.
*/
partrel = heap_open(PartitionRuleRelationId, AccessShareLock);
tuple = caql_getfirst(caql_addrel(cqclr(&cqc), partrel),
cql("SELECT * FROM pg_partition_rule "
" WHERE parchildrelid = :1 ",
ObjectIdGetDatum(relid)));
Assert(HeapTupleIsValid(tuple));
name = ((Form_pg_partition_rule)GETSTRUCT(tuple))->parname;
parname = pstrdup(NameStr(name));
position = ((Form_pg_partition_rule)GETSTRUCT(tuple))->parruleord;
paroid = ((Form_pg_partition_rule)GETSTRUCT(tuple))->paroid;
heap_freetuple(tuple);
heap_close(partrel, NoLock);
partrel = heap_open(PartitionRelationId, AccessShareLock);
tuple = caql_getfirst(caql_addrel(cqclr(&cqc), partrel),
cql("SELECT parlevel FROM pg_partition "
" WHERE oid = :1 ",
ObjectIdGetDatum(paroid)));
Assert(HeapTupleIsValid(tuple));
depth = ((Form_pg_partition)GETSTRUCT(tuple))->parlevel + 1;
heap_freetuple(tuple);
heap_close(partrel, NoLock);
heap_close(crel, NoLock);
/* now, build the piece to append */
snprintf(depthstr, sizeof(depthstr), "%d", depth);
if (strlen(parname) == 0)
snprintf(prtstr, sizeof(prtstr), "prt_%d", position);
else
snprintf(prtstr, sizeof(prtstr), "prt_%s", parname);
chidx->idxname = ChooseRelationName(stmt->idxname, depthstr, /* depth */
prtstr, /* part spec */
nspOid, nameCache);
*extras_after = lappend(*extras_after, chidx);
}
}
heap_close(rel, AccessShareLock);
}
/* take care of the where clause */
if (stmt->whereClause) {
/*
* Put the parent table into the rtable so that the WHERE clause can
* refer to its fields without qualification. Note that this only
* works if the parent table already exists --- so we can't easily
* support predicates on indexes created implicitly by CREATE TABLE.
* Fortunately, that's not necessary.
*/
rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
/* no to join list, yes to namespaces */
addRTEtoQuery(pstate, rte, false, true, true);
stmt->whereClause =
transformWhereClause(pstate, stmt->whereClause, "WHERE");
}
/* take care of any index expressions */
foreach (l, stmt->indexParams) {
IndexElem *ielem = (IndexElem *)lfirst(l);
if (ielem->expr) {
/* Set up rtable as for predicate, see notes above */
if (rte == NULL) {
rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
/* no to join list, yes to namespaces */
addRTEtoQuery(pstate, rte, false, true, true);
}
ielem->expr = transformExpr(pstate, ielem->expr);
/*
* We check only that the result type is legitimate; this is for
* consistency with what transformWhereClause() checks for the
* predicate. DefineIndex() will make more checks.
*/
if (expression_returns_set(ielem->expr))
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("index expression may not return a set"),
errOmitLocation(true)));
}
}
qry->hasSubLinks = pstate->p_hasSubLinks;
stmt->rangetable = pstate->p_rtable;
qry->utilityStmt = (Node *)stmt;
return qry;
}
/*
* transformRuleStmt -
* transform a Create Rule Statement. The actions is a list of parse
* trees which is transformed into a list of query trees.
*/
static Query *transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
List **extras_before, List **extras_after) {
Query *qry;
Relation rel;
RangeTblEntry *oldrte;
RangeTblEntry *newrte;
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
qry->utilityStmt = (Node *)stmt;
/*
* To avoid deadlock, make sure the first thing we do is grab
* AccessExclusiveLock on the target relation. This will be needed by
* DefineQueryRewrite(), and we don't want to grab a lesser lock
* beforehand.
*/
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
/*
* NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
* Set up their RTEs in the main pstate for use in parsing the rule
* qualification.
*/
Assert(pstate->p_rtable == NIL);
oldrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*OLD*", NIL),
false, false);
newrte = addRangeTableEntryForRelation(pstate, rel, makeAlias("*NEW*", NIL),
false, false);
/* Must override addRangeTableEntry's default access-check flags */
oldrte->requiredPerms = 0;
newrte->requiredPerms = 0;
/*
* They must be in the namespace too for lookup purposes, but only add the
* one(s) that are relevant for the current kind of rule. In an UPDATE
* rule, quals must refer to OLD.field or NEW.field to be unambiguous, but
* there's no need to be so picky for INSERT & DELETE. We do not add them
* to the joinlist.
*/
switch (stmt->event) {
case CMD_SELECT:
addRTEtoQuery(pstate, oldrte, false, true, true);
break;
case CMD_UPDATE:
addRTEtoQuery(pstate, oldrte, false, true, true);
addRTEtoQuery(pstate, newrte, false, true, true);
break;
case CMD_INSERT:
addRTEtoQuery(pstate, newrte, false, true, true);
break;
case CMD_DELETE:
addRTEtoQuery(pstate, oldrte, false, true, true);
break;
default:
elog(ERROR, "unrecognized event type: %d", (int)stmt->event);
break;
}
/* take care of the where clause */
stmt->whereClause = transformWhereClause(pstate, stmt->whereClause, "WHERE");
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg(
"rule WHERE condition may not contain references to "
"other relations"),
errOmitLocation(true)));
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in rule WHERE condition"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in rule WHERE condition"),
errOmitLocation(true)));
/* save info about sublinks in where clause */
qry->hasSubLinks = pstate->p_hasSubLinks;
/*
* 'instead nothing' rules with a qualification need a query rangetable so
* the rewrite handler can add the negated rule qualification to the
* original query. We create a query with the new command type CMD_NOTHING
* here that is treated specially by the rewrite system.
*/
if (stmt->actions == NIL) {
Query *nothing_qry = makeNode(Query);
nothing_qry->commandType = CMD_NOTHING;
nothing_qry->rtable = pstate->p_rtable;
nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */
stmt->actions = list_make1(nothing_qry);
} else {
ListCell *l;
List *newactions = NIL;
/*
* transform each statement, like parse_sub_analyze()
*/
foreach (l, stmt->actions) {
Node *action = (Node *)lfirst(l);
ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
Query *sub_qry, *top_subqry;
bool has_old, has_new;
/*
* Set up OLD/NEW in the rtable for this statement. The entries
* are added only to relnamespace, not varnamespace, because we
* don't want them to be referred to by unqualified field names
* nor "*" in the rule actions. We decide later whether to put
* them in the joinlist.
*/
oldrte = addRangeTableEntryForRelation(
sub_pstate, rel, makeAlias("*OLD*", NIL), false, false);
newrte = addRangeTableEntryForRelation(
sub_pstate, rel, makeAlias("*NEW*", NIL), false, false);
oldrte->requiredPerms = 0;
newrte->requiredPerms = 0;
addRTEtoQuery(sub_pstate, oldrte, false, true, false);
addRTEtoQuery(sub_pstate, newrte, false, true, false);
/* Transform the rule action statement */
top_subqry =
transformStmt(sub_pstate, action, extras_before, extras_after);
/*
* We cannot support utility-statement actions (eg NOTIFY) with
* nonempty rule WHERE conditions, because there's no way to make
* the utility action execute conditionally.
*/
if (top_subqry->commandType == CMD_UTILITY && stmt->whereClause != NULL)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg(
"rules with WHERE conditions may only have SELECT, "
"INSERT, UPDATE, or DELETE actions"),
errOmitLocation(true)));
/*
* 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.)
*/
sub_qry = getInsertSelectQuery(top_subqry, NULL);
/*
* If the sub_qry is a setop, we cannot attach any qualifications
* to it, because the planner won't notice them. This could
* perhaps be relaxed someday, but for now, we may as well reject
* such a rule immediately.
*/
if (sub_qry->setOperations != NULL && stmt->whereClause != NULL)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"conditional UNION/INTERSECT/EXCEPT statements are "
"not implemented"),
errOmitLocation(true)));
/*
* Validate action's use of OLD/NEW, qual too
*/
has_old = rangeTableEntry_used((Node *)sub_qry, PRS2_OLD_VARNO, 0) ||
rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
has_new = rangeTableEntry_used((Node *)sub_qry, PRS2_NEW_VARNO, 0) ||
rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
switch (stmt->event) {
case CMD_SELECT:
if (has_old)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("ON SELECT rule may not use OLD"),
errOmitLocation(true)));
if (has_new)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("ON SELECT rule may not use NEW"),
errOmitLocation(true)));
break;
case CMD_UPDATE:
/* both are OK */
break;
case CMD_INSERT:
if (has_old)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("ON INSERT rule may not use OLD"),
errOmitLocation(true)));
break;
case CMD_DELETE:
if (has_new)
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("ON DELETE rule may not use NEW"),
errOmitLocation(true)));
break;
default:
elog(ERROR, "unrecognized event type: %d", (int)stmt->event);
break;
}
/*
* For efficiency's sake, add OLD to the rule action's jointree
* only if it was actually referenced in the statement or qual.
*
* For INSERT, NEW is not really a relation (only a reference to
* the to-be-inserted tuple) and should never be added to the
* jointree.
*
* For UPDATE, we treat NEW as being another kind of reference to
* OLD, because it represents references to *transformed* tuples
* of the existing relation. It would be wrong to enter NEW
* separately in the jointree, since that would cause a double
* join of the updated relation. It's also wrong to fail to make
* a jointree entry if only NEW and not OLD is mentioned.
*/
if (has_old || (has_new && stmt->event == CMD_UPDATE)) {
/*
* If sub_qry is a setop, manipulating its jointree will do no
* good at all, because the jointree is dummy. (This should be
* a can't-happen case because of prior tests.)
*/
if (sub_qry->setOperations != NULL)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"conditional UNION/INTERSECT/EXCEPT statements "
"are not implemented"),
errOmitLocation(true)));
/* hack so we can use addRTEtoQuery() */
sub_pstate->p_rtable = sub_qry->rtable;
sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
addRTEtoQuery(sub_pstate, oldrte, true, false, false);
sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
}
newactions = lappend(newactions, top_subqry);
release_pstate_resources(sub_pstate);
free_parsestate(&sub_pstate);
}
stmt->actions = newactions;
}
/* Close relation, but keep the exclusive lock */
heap_close(rel, NoLock);
return qry;
}
/*
* If an input query (Q) mixes window functions with aggregate
* functions or grouping, then (per SQL:2003) we need to divide
* it into an outer query, Q', that contains no aggregate calls
* or grouping and an inner query, Q'', that contains no window
* calls.
*
* Q' will have a 1-entry range table whose entry corresponds to
* the results of Q''.
*
* Q'' will have the same range as Q and will be pushed down into
* a subquery range table entry in Q'.
*
* As a result, the depth of outer references in Q'' and below
* will increase, so we need to adjust non-zero xxxlevelsup fields
* (Var, Aggref, and WindowRef nodes) in Q'' and below. At the end,
* there will be no levelsup items referring to Q'. Prior references
* to Q will now refer to Q''; prior references to blocks above Q will
* refer to the same blocks above Q'.)
*
* We do all this by creating a new Query node, subq, for Q''. We
* modify the input Query node, qry, in place for Q'. (Since qry is
* also the input, Q, be careful not to destroy values before we're
* done with them.
*/
static Query *transformGroupedWindows(Query *qry) {
Query *subq;
RangeTblEntry *rte;
RangeTblRef *ref;
Alias *alias;
bool hadSubLinks = qry->hasSubLinks;
grouped_window_ctx ctx;
Assert(qry->commandType == CMD_SELECT);
Assert(!PointerIsValid(qry->utilityStmt));
Assert(qry->returningList == NIL);
if (!qry->hasWindFuncs || !(qry->groupClause || qry->hasAggs)) return qry;
/* Make the new subquery (Q''). Note that (per SQL:2003) there
* can't be any window functions called in the WHERE, GROUP BY,
* or HAVING clauses.
*/
subq = makeNode(Query);
subq->commandType = CMD_SELECT;
subq->querySource = QSRC_PARSER;
subq->canSetTag = true;
subq->utilityStmt = NULL;
subq->resultRelation = 0;
subq->intoClause = NULL;
subq->hasAggs = qry->hasAggs;
subq->hasWindFuncs = false; /* reevaluate later */
subq->hasSubLinks = qry->hasSubLinks; /* reevaluate later */
/* Core of subquery input table expression: */
subq->rtable = qry->rtable; /* before windowing */
subq->jointree = qry->jointree; /* before windowing */
subq->targetList = NIL; /* fill in later */
subq->returningList = NIL;
subq->groupClause = qry->groupClause; /* before windowing */
subq->havingQual = qry->havingQual; /* before windowing */
subq->windowClause = NIL; /* by construction */
subq->distinctClause = NIL; /* after windowing */
subq->sortClause = NIL; /* after windowing */
subq->limitOffset = NULL; /* after windowing */
subq->limitCount = NULL; /* after windowing */
subq->rowMarks = NIL;
subq->setOperations = NULL;
/* Check if there is a window function in the join tree. If so
* we must mark hasWindFuncs in the sub query as well.
*/
if (checkExprHasWindFuncs((Node *)subq->jointree)) subq->hasWindFuncs = true;
/* Make the single range table entry for the outer query Q' as
* a wrapper for the subquery (Q'') currently under construction.
*/
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_SUBQUERY;
rte->subquery = subq;
rte->alias = NULL; /* fill in later */
rte->eref = NULL; /* fill in later */
rte->inFromCl = true;
rte->requiredPerms = ACL_SELECT;
/* Default?
* rte->inh = 0;
* rte->checkAsUser = 0;
* rte->pseudocols = 0;
*/
/* Make a reference to the new range table entry .
*/
ref = makeNode(RangeTblRef);
ref->rtindex = 1;
/* Set up context for mutating the target list. Careful.
* This is trickier than it looks. The context will be
* "primed" with grouping targets.
*/
init_grouped_window_context(&ctx, qry);
/* Begin rewriting the outer query in place.
*/
qry->hasAggs = false; /* by constuction */
/* qry->hasSubLinks -- reevaluate later. */
/* Core of outer query input table expression: */
qry->rtable = list_make1(rte);
qry->jointree = (FromExpr *)makeNode(FromExpr);
qry->jointree->fromlist = list_make1(ref);
qry->jointree->quals = NULL;
/* qry->targetList -- to be mutated from Q to Q' below */
qry->groupClause = NIL; /* by construction */
qry->havingQual = NULL; /* by construction */
/* Mutate the Q target list and windowClauses for use in Q' and, at the
* same time, update state with info needed to assemble the target list
* for the subquery (Q'').
*/
qry->targetList =
(List *)grouped_window_mutator((Node *)qry->targetList, &ctx);
qry->windowClause =
(List *)grouped_window_mutator((Node *)qry->windowClause, &ctx);
qry->hasSubLinks = checkExprHasSubLink((Node *)qry->targetList);
/* New subquery fields
*/
subq->targetList = ctx.subtlist;
subq->groupClause = ctx.subgroupClause;
/* We always need an eref, but we shouldn't really need a filled in alias.
* However, view deparse (or at least the fix for MPP-2189) wants one.
*/
alias = make_replacement_alias(subq, "Window");
rte->eref = copyObject(alias);
rte->alias = alias;
/* Accomodate depth change in new subquery, Q''.
*/
IncrementVarSublevelsUpInTransformGroupedWindows((Node *)subq, 1, 1);
/* Might have changed. */
subq->hasSubLinks = checkExprHasSubLink((Node *)subq);
Assert(PointerIsValid(qry->targetList));
Assert(IsA(qry->targetList, List));
/* Use error instead of assertion to "use" hadSubLinks and keep compiler
* happy. */
if (hadSubLinks != (qry->hasSubLinks || subq->hasSubLinks))
elog(ERROR,
"inconsistency detected in internal grouped windows transformation");
discard_grouped_window_context(&ctx);
return qry;
}
/* Helper for transformGroupedWindows:
*
* Prime the subquery target list in the context with the grouping
* and windowing attributes from the given query and adjust the
* subquery group clauses in the context to agree.
*
* Note that we arrange dense sortgroupref values and stash the
* referents on the front of the subquery target list. This may
* be over-kill, but the grouping extension code seems to like it
* this way.
*
* Note that we only transfer sortgrpref values associated with
* grouping and windowing to the subquery context. The subquery
* shouldn't care about ordering, etc. XXX
*/
static void init_grouped_window_context(grouped_window_ctx *ctx, Query *qry) {
List *grp_tles = NIL;
ListCell *lc = NULL;
Index maxsgr = 0;
grp_tles = get_sortgroupclauses_tles(qry->groupClause, qry->targetList);
maxsgr = maxSortGroupRef(grp_tles, true);
ctx->subtlist = NIL;
ctx->subgroupClause = NIL;
/* Set up scratch space.
*/
ctx->subrtable = qry->rtable;
/* Map input = outer query sortgroupref values to subquery values while
* building the
* subquery target list prefix. */
ctx->sgr_map = palloc0((maxsgr + 1) * sizeof(ctx->sgr_map[0]));
foreach (lc, grp_tles) {
TargetEntry *tle;
Index old_sgr;
tle = (TargetEntry *)copyObject(lfirst(lc));
old_sgr = tle->ressortgroupref;
ctx->subtlist = lappend(ctx->subtlist, tle);
tle->resno = list_length(ctx->subtlist);
tle->ressortgroupref = tle->resno;
tle->resjunk = false;
ctx->sgr_map[old_sgr] = tle->ressortgroupref;
}
/* Miscellaneous scratch area. */
ctx->call_depth = 0;
ctx->tle = NULL;
/* Revise grouping into ctx->subgroupClause */
ctx->subgroupClause = (List *)map_sgr_mutator((Node *)qry->groupClause, ctx);
}
/* Helper for transformGroupedWindows */
static void discard_grouped_window_context(grouped_window_ctx *ctx) {
ctx->subtlist = NIL;
ctx->subgroupClause = NIL;
ctx->tle = NULL;
if (ctx->sgr_map) pfree(ctx->sgr_map);
ctx->sgr_map = NULL;
ctx->subrtable = NULL;
}
/* Helper for transformGroupedWindows:
*
* Look for the given expression in the context's subtlist. If
* none is found and the force argument is true, add a target
* for it. Make and return a variable referring to the target
* with the matching expression, or return NULL, if no target
* was found/added.
*/
static Var *var_for_gw_expr(grouped_window_ctx *ctx, Node *expr, bool force) {
Var *var = NULL;
TargetEntry *tle = tlist_member(expr, ctx->subtlist);
if (tle == NULL && force) {
tle = makeNode(TargetEntry);
ctx->subtlist = lappend(ctx->subtlist, tle);
tle->expr = (Expr *)expr;
tle->resno = list_length(ctx->subtlist);
/* See comment in grouped_window_mutator for why level 3 is appropriate. */
if (ctx->call_depth == 3 && ctx->tle != NULL && ctx->tle->resname != NULL) {
tle->resname = pstrdup(ctx->tle->resname);
} else {
tle->resname = generate_positional_name(tle->resno);
}
tle->ressortgroupref = 0;
tle->resorigtbl = 0;
tle->resorigcol = 0;
tle->resjunk = false;
}
if (tle != NULL) {
var = makeNode(Var);
var->varno = 1; /* one and only */
var->varattno = tle->resno; /* by construction */
var->vartype = exprType((Node *)tle->expr);
var->vartypmod = exprTypmod((Node *)tle->expr);
var->varlevelsup = 0;
var->varnoold = 1;
var->varoattno = tle->resno;
var->location = 0;
}
return var;
}
/* Helper for transformGroupedWindows:
*
* Mutator for subquery groupingClause to adjust sortgrpref values
* based on map developed while priming context target list.
*/
static Node *map_sgr_mutator(Node *node, void *context) {
grouped_window_ctx *ctx = (grouped_window_ctx *)context;
if (!node) return NULL;
if (IsA(node, List)) {
ListCell *lc;
List *new_lst = NIL;
foreach (lc, (List *)node) {
Node *newnode = lfirst(lc);
newnode = map_sgr_mutator(newnode, ctx);
new_lst = lappend(new_lst, newnode);
}
return (Node *)new_lst;
}
else if (IsA(node, GroupClause)) {
GroupClause *g = (GroupClause *)node;
GroupClause *new_g = makeNode(GroupClause);
memcpy(new_g, g, sizeof(GroupClause));
new_g->tleSortGroupRef = ctx->sgr_map[g->tleSortGroupRef];
return (Node *)new_g;
}
/* Just like above, but don't assume identical */
else if (IsA(node, SortClause)) {
SortClause *s = (SortClause *)node;
SortClause *new_s = makeNode(SortClause);
memcpy(new_s, s, sizeof(SortClause));
new_s->tleSortGroupRef = ctx->sgr_map[s->tleSortGroupRef];
return (Node *)new_s;
}
else if (IsA(node, GroupingClause)) {
GroupingClause *gc = (GroupingClause *)node;
GroupingClause *new_gc = makeNode(GroupingClause);
memcpy(new_gc, gc, sizeof(GroupingClause));
new_gc->groupsets = (List *)map_sgr_mutator((Node *)gc->groupsets, ctx);
return (Node *)new_gc;
}
return NULL; /* Never happens */
}
/*
* Helper for transformGroupedWindows:
*
* Transform targets from Q into targets for Q' and place information
* needed to eventually construct the target list for the subquery Q''
* in the context structure.
*
* The general idea is to add expressions that must be evaluated in the
* subquery to the subquery target list (in the context) and to replace
* them with Var nodes in the outer query.
*
* If there are any Agg nodes in the Q'' target list, arrange
* to set hasAggs to true in the subquery. (This should already be
* done, though).
*
* If we're pushing down an entire TLE that has a resname, use
* it as an alias in the upper TLE, too. Facilitate this by copying
* down the resname from an immediately enclosing TargetEntry, if any.
*
* The algorithm repeatedly searches the subquery target list under
* construction (quadric), however we don't expect many targets so
* we don't optimize this. (Could, for example, use a hash or divide
* the target list into var, expr, and group/aggregate function lists.)
*/
static Node *grouped_window_mutator(Node *node, void *context) {
Node *result = NULL;
grouped_window_ctx *ctx = (grouped_window_ctx *)context;
if (!node) return result;
ctx->call_depth++;
if (IsA(node, TargetEntry)) {
TargetEntry *tle = (TargetEntry *)node;
TargetEntry *new_tle = makeNode(TargetEntry);
/* Copy the target entry. */
new_tle->resno = tle->resno;
if (tle->resname == NULL) {
new_tle->resname = generate_positional_name(new_tle->resno);
} else {
new_tle->resname = pstrdup(tle->resname);
}
new_tle->ressortgroupref = tle->ressortgroupref;
new_tle->resorigtbl = InvalidOid;
new_tle->resorigcol = 0;
new_tle->resjunk = tle->resjunk;
/* This is pretty shady, but we know our call pattern. The target
* list is at level 1, so we're interested in target entries at level
* 2. We record them in context so var_for_gw_expr can maybe make a better
* than default choice of alias.
*/
if (ctx->call_depth == 2) {
ctx->tle = tle;
} else {
ctx->tle = NULL;
}
new_tle->expr = (Expr *)grouped_window_mutator((Node *)tle->expr, ctx);
ctx->tle = NULL;
result = (Node *)new_tle;
} else if (IsA(node, Aggref) || IsA(node, PercentileExpr) ||
IsA(node, GroupingFunc) || IsA(node, GroupId)) {
/* Aggregation expression */
result = (Node *)var_for_gw_expr(ctx, node, true);
} else if (IsA(node, Var)) {
Var *var = (Var *)node;
/* Since this is a Var (leaf node), we must be able to mutate it,
* else we can't finish the transformation and must give up.
*/
result = (Node *)var_for_gw_expr(ctx, node, false);
if (!result) {
List *altvars = generate_alternate_vars(var, ctx);
ListCell *lc;
foreach (lc, altvars) {
result = (Node *)var_for_gw_expr(ctx, lfirst(lc), false);
if (result) break;
}
}
if (!result)
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR),
errmsg("unresolved grouping key in window query"),
errhint(
"You may need to use explicit aliases and/or to "
"refer to grouping "
"keys in the same way throughout the query."),
errOmitLocation(true)));
} else {
/* Grouping expression; may not find one. */
result = (Node *)var_for_gw_expr(ctx, node, false);
}
if (!result) {
result = expression_tree_mutator(node, grouped_window_mutator, ctx);
}
ctx->call_depth--;
return result;
}
/*
* Helper for transformGroupedWindows:
*
* Build an Alias for a subquery RTE representing the given Query.
* The input string aname is the name for the overall Alias. The
* attribute names are all found or made up.
*/
static Alias *make_replacement_alias(Query *qry, const char *aname) {
ListCell *lc = NULL;
char *name = NULL;
Alias *alias = makeNode(Alias);
AttrNumber attrno = 0;
alias->aliasname = pstrdup(aname);
alias->colnames = NIL;
foreach (lc, qry->targetList) {
TargetEntry *tle = (TargetEntry *)lfirst(lc);
attrno++;
if (tle->resname) {
/* Prefer the target's resname. */
name = pstrdup(tle->resname);
} else if (IsA(tle->expr, Var)) {
/* If the target expression is a Var, use the name of the
* attribute in the query's range table. */
Var *var = (Var *)tle->expr;
RangeTblEntry *rte = rt_fetch(var->varno, qry->rtable);
name = pstrdup(get_rte_attribute_name(rte, var->varattno));
} else {
/* If all else, fails, generate a name based on position. */
name = generate_positional_name(attrno);
}
alias->colnames = lappend(alias->colnames, makeString(name));
}
return alias;
}
/*
* Helper for transformGroupedWindows:
*
* Make a palloc'd C-string named for the input attribute number.
*/
static char *generate_positional_name(AttrNumber attrno) {
int rc = 0;
char buf[NAMEDATALEN];
rc = snprintf(buf, sizeof(buf), "att_%d", attrno);
if (rc == EOF || rc < 0 || rc >= sizeof(buf)) {
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg("can't generate internal attribute name"),
errOmitLocation(true)));
}
return pstrdup(buf);
}
/*
* Helper for transformGroupedWindows:
*
* Find alternate Vars on the range of the input query that are aliases
* (modulo ANSI join) of the input Var on the range and that occur in the
* target list of the input query.
*
* If the input Var references a join result, there will be a single
* alias. If not, we need to search the range table for occurances
* of the input Var in some join result's RTE and add a Var referring
* to the appropriate attribute of the join RTE to the list.
*
* This is not efficient, but the need is rare (MPP-12082) so we don't
* bother to precompute this.
*/
static List *generate_alternate_vars(Var *invar, grouped_window_ctx *ctx) {
List *rtable = ctx->subrtable;
RangeTblEntry *inrte;
List *alternates = NIL;
Assert(IsA(invar, Var));
inrte = rt_fetch(invar->varno, rtable);
if (inrte->rtekind == RTE_JOIN) {
Node *ja = list_nth(inrte->joinaliasvars, invar->varattno - 1);
/* Though Node types other than Var (e.g., CoalesceExpr or Const) may occur
* as joinaliasvars, we ignore them.
*/
if (IsA(ja, Var)) {
alternates = lappend(alternates, copyObject(ja));
}
} else {
ListCell *jlc;
Index varno = 0;
foreach (jlc, rtable) {
RangeTblEntry *rte = (RangeTblEntry *)lfirst(jlc);
varno++; /* This RTE's varno */
if (rte->rtekind == RTE_JOIN) {
ListCell *alc;
AttrNumber attno = 0;
foreach (alc, rte->joinaliasvars) {
ListCell *tlc;
Node *altnode = lfirst(alc);
Var *altvar = (Var *)altnode;
attno++; /* This attribute's attno in its join RTE */
if (!IsA(altvar, Var) || !equal(invar, altvar)) continue;
/* Look for a matching Var in the target list. */
foreach (tlc, ctx->subtlist) {
TargetEntry *tle = (TargetEntry *)lfirst(tlc);
Var *v = (Var *)tle->expr;
if (IsA(v, Var) && v->varno == varno && v->varattno == attno) {
alternates = lappend(alternates, tle->expr);
}
}
}
}
}
}
return alternates;
}
/*
* transformSelectStmt -
* transforms a Select Statement
*
* Note: this is also used for DECLARE CURSOR statements.
*/
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt) {
Query *qry = makeNode(Query);
Node *qual;
ListCell *l;
qry->commandType = CMD_SELECT;
/* setup database name for use of magma operations */
MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
database = get_database_name(MyDatabaseId);
MemoryContextSwitchTo(oldContext);
/* process the WITH clause */
if (stmt->withClause != NULL) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
/*
* Put WINDOW clause data into pstate so that window references know
* about them.
*/
pstate->p_win_clauses = stmt->windowClause;
/* process the FROM clause */
transformFromClause(pstate, stmt->fromClause);
/* tidy up expressions in window clauses */
transformWindowSpecExprs(pstate);
/* transform targetlist */
qry->targetList = transformTargetList(pstate, stmt->targetList);
/* mark column origins */
markTargetListOrigins(pstate, qry->targetList);
/* transform WHERE */
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
/*
* Initial processing of HAVING clause is just like WHERE clause.
*/
pstate->having_qual =
transformWhereClause(pstate, stmt->havingClause, "HAVING");
/*
* CDB: Untyped Const or Param nodes in a subquery in the FROM clause
* might have been assigned proper types when we transformed the WHERE
* clause, targetlist, etc. Bring targetlist Var types up to date.
*/
fixup_unknown_vars_in_targetlist(pstate, qry->targetList);
/*
* Transform sorting/grouping stuff. Do ORDER BY first because both
* transformGroupClause and transformDistinctClause need the results.
*/
qry->sortClause = transformSortClause(
pstate, stmt->sortClause, &qry->targetList, true, /* fix unknowns */
false /* use SQL92 rules */);
qry->groupClause =
transformGroupClause(pstate, stmt->groupClause, &qry->targetList,
qry->sortClause, false /* useSQL92 rules */);
/*
* SCATTER BY clause on a table function TableValueExpr subquery.
*
* Note: a given subquery cannot have both a SCATTER clause and an INTO
* clause, because both of those control distribution. This should not
* possible due to grammar restrictions on where a SCATTER clause is
* allowed.
*/
Insist(!(stmt->scatterClause && stmt->intoClause));
qry->scatterClause =
transformScatterClause(pstate, stmt->scatterClause, &qry->targetList);
/* Having clause */
qry->havingQual = pstate->having_qual;
pstate->having_qual = NULL;
/*
* Process WINDOW clause.
*/
transformWindowClause(pstate, qry);
qry->distinctClause =
transformDistinctClause(pstate, stmt->distinctClause, &qry->targetList,
&qry->sortClause, &qry->groupClause);
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount, "LIMIT");
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
/* handle any SELECT INTO/CREATE TABLE AS spec */
qry->intoClause = NULL;
if (stmt->intoClause) {
qry->intoClause = stmt->intoClause;
if (stmt->intoClause->colNames)
applyColumnNames(qry->targetList, stmt->intoClause->colNames);
/* XXX XXX: qry->partitionBy = stmt->partitionBy; */
}
/*
* Generally, we'll only have a distributedBy clause if stmt->into is set,
* with the exception of set op queries, since transformSetOperationStmt()
* sets stmt->into to NULL to avoid complications elsewhere.
*/
if (Gp_role == GP_ROLE_DISPATCH) setQryDistributionPolicy(stmt, qry);
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);
qry->hasWindFuncs = pstate->p_hasWindFuncs;
if (pstate->p_hasWindFuncs) parseProcessWindFuncs(pstate, qry);
foreach (l, stmt->lockingClause) {
/* disable select for update/share for gpsql */
ereport(ERROR,
(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("Cannot support select for update/share statement yet")));
/*transformLockingClause(qry, (LockingClause *) lfirst(l));*/
}
/*
* If the query mixes window functions and aggregates, we need to
* transform it such that the grouped query appears as a subquery
*/
if (qry->hasWindFuncs && (qry->groupClause || qry->hasAggs))
transformGroupedWindows(qry);
return qry;
}
/*
* transformValuesClause -
* transforms a VALUES clause that's being used as a standalone SELECT
*
* We build a Query containing a VALUES RTE, rather as if one had written
* SELECT * FROM (VALUES ...)
*/
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt) {
Query *qry = makeNode(Query);
List *exprsLists = NIL;
List **coltype_lists = NULL;
Oid *coltypes = NULL;
int sublist_length = -1;
List *newExprsLists;
RangeTblEntry *rte;
RangeTblRef *rtr;
ListCell *lc;
ListCell *lc2;
int i;
qry->commandType = CMD_SELECT;
/* Most SELECT stuff doesn't apply in a VALUES clause */
Assert(stmt->distinctClause == NIL);
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
Assert(stmt->groupClause == NIL);
Assert(stmt->havingClause == NULL);
Assert(stmt->scatterClause == NIL);
Assert(stmt->op == SETOP_NONE);
/* process the WITH clause independently of all else */
if (stmt->withClause != NULL) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* For each row of VALUES, transform the raw expressions and gather type
* information. This is also a handy place to reject DEFAULT nodes, which
* the grammar allows for simplicity.
*/
foreach (lc, stmt->valuesLists) {
List *sublist = (List *)lfirst(lc);
/* Do basic expression transformation (same as a ROW() expr) */
sublist = transformExpressionList(pstate, sublist);
/*
* All the sublists must be the same length, *after* transformation
* (which might expand '*' into multiple items). The VALUES RTE can't
* handle anything different.
*/
if (sublist_length < 0) {
/* Remember post-transformation length of first sublist */
sublist_length = list_length(sublist);
/* and allocate arrays for column-type info */
coltype_lists = (List **)palloc0(sublist_length * sizeof(List *));
coltypes = (Oid *)palloc0(sublist_length * sizeof(Oid));
} else if (sublist_length != list_length(sublist)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("VALUES lists must all be the same length"),
errOmitLocation(true)));
}
exprsLists = lappend(exprsLists, sublist);
i = 0;
foreach (lc2, sublist) {
Node *col = (Node *)lfirst(lc2);
if (IsA(col, SetToDefault))
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("DEFAULT can only appear in a VALUES list within INSERT"),
errOmitLocation(true)));
coltype_lists[i] = lappend_oid(coltype_lists[i], exprType(col));
i++;
}
}
/*
* Now resolve the common types of the columns, and coerce everything to
* those types.
*/
for (i = 0; i < sublist_length; i++) {
coltypes[i] = select_common_type(coltype_lists[i], "VALUES");
}
newExprsLists = NIL;
foreach (lc, exprsLists) {
List *sublist = (List *)lfirst(lc);
List *newsublist = NIL;
i = 0;
foreach (lc2, sublist) {
Node *col = (Node *)lfirst(lc2);
col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES");
newsublist = lappend(newsublist, col);
i++;
}
newExprsLists = lappend(newExprsLists, newsublist);
}
/*
* Generate the VALUES RTE
*/
rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true);
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
/*
* Generate a targetlist as though expanding "*"
*/
Assert(pstate->p_next_resno == 1);
qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);
/*
* The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
* VALUES, so cope.
*/
qry->sortClause = transformSortClause(
pstate, stmt->sortClause, &qry->targetList, true, /* fix unknowns */
false /* use SQL92 rules */);
qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount, "LIMIT");
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
if (stmt->lockingClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
errOmitLocation(true)));
if (Gp_role == GP_ROLE_DISPATCH) setQryDistributionPolicy(stmt, qry);
/* handle any CREATE TABLE AS spec */
qry->intoClause = NULL;
if (stmt->intoClause) {
qry->intoClause = stmt->intoClause;
if (stmt->intoClause->colNames)
applyColumnNames(qry->targetList, stmt->intoClause->colNames);
}
/*
* There mustn't have been any table references in the expressions, else
* strange things would happen, like Cartesian products of those tables
* with the VALUES list. We have to check this after parsing ORDER BY et
* al since those could insert more junk.
*/
if (list_length(pstate->p_joinlist) != 1)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain table references"),
errOmitLocation(true)));
/*
* Another thing we can't currently support is NEW/OLD references in rules
* --- seems we'd need something like SQL99's LATERAL construct to ensure
* that the values would be available while evaluating the VALUES RTE.
* This is a shame. FIXME
*/
if (list_length(pstate->p_rtable) != 1 &&
contain_vars_of_level((Node *)newExprsLists, 0))
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VALUES must not contain OLD or NEW references"),
errhint("Use SELECT ... UNION ALL ... instead."),
errOmitLocation(true)));
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in VALUES"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in VALUES"),
errOmitLocation(true)));
return qry;
}
/*
* transformSetOperationStmt -
* transforms a set-operations tree
*
* A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
* structure to it. We must transform each leaf SELECT and build up a top-
* level Query that contains the leaf SELECTs as subqueries in its rangetable.
* The tree of set operations is converted into the setOperations field of
* the top-level Query.
*/
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) {
Query *qry = makeNode(Query);
SelectStmt *leftmostSelect;
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
List *intoColNames = NIL;
List *sortClause;
Node *limitOffset;
Node *limitCount;
List *lockingClause;
Node *node;
ListCell *left_tlist, *lct, *lcm, *l;
List *targetvars, *targetnames, *sv_relnamespace, *sv_varnamespace,
*sv_rtable;
RangeTblEntry *jrte;
int tllen;
List *colTypes, *colTypmods;
qry->commandType = CMD_SELECT;
/* process the WITH clause independently of all else */
if (stmt->withClause != NULL) {
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/*
* Find leftmost leaf SelectStmt; extract the one-time-only items from it
* and from the top-level node. (Most of the INTO options can be
* transferred to the Query immediately, but intoColNames has to be saved
* to apply below.)
*/
leftmostSelect = stmt->larg;
while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
leftmostSelect = leftmostSelect->larg;
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
leftmostSelect->larg == NULL);
qry->intoClause = NULL;
if (leftmostSelect->intoClause) {
qry->intoClause = leftmostSelect->intoClause;
intoColNames = leftmostSelect->intoClause->colNames;
}
/* clear this to prevent complaints in transformSetOperationTree() */
leftmostSelect->intoClause = NULL;
/*
* These are not one-time, exactly, but we want to process them here and
* not let transformSetOperationTree() see them --- else it'll just
* recurse right back here!
*/
sortClause = stmt->sortClause;
limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount;
lockingClause = stmt->lockingClause;
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
stmt->lockingClause = NIL;
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (lockingClause)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with "
"UNION/INTERSECT/EXCEPT"),
errOmitLocation(true)));
/*
* Before transforming the subtrees, we collect all the data types
* and typmods by searching their targetList (ResTarget) or valuesClause.
* This is necessary because choosing column types in leaf query
* without knowing whole of tree may result in a wrong type. And this
* situation goes an error that is against user's instinct. Instead,
* we want to look at all the leavs at one time, and decide each column
* types. The resjunk columns are not interesting, because type coercion
* between queries is done only for each non-resjunk column in set operations.
*/
colTypes = NIL;
colTypmods = NIL;
pstate->p_setopTypes = NIL;
pstate->p_setopTypmods = NIL;
collectSetopTypes(pstate, stmt, &colTypes, &colTypmods);
Insist(list_length(colTypes) == list_length(colTypmods));
forboth(lct, colTypes, lcm, colTypmods) {
List *types = (List *)lfirst(lct);
List *typmods = (List *)lfirst(lcm);
ListCell *lct2, *lcm2;
Oid ptype;
int32 ptypmod;
Oid restype;
int32 restypmod;
bool allsame, hasnontext;
char *context;
Insist(list_length(types) == list_length(typmods));
context = (stmt->op == SETOP_UNION ? "UNION" : stmt->op == SETOP_INTERSECT
? "INTERSECT"
: "EXCEPT");
allsame = true;
hasnontext = false;
ptype = linitial_oid(types);
ptypmod = linitial_int(typmods);
forboth(lct2, types, lcm2, typmods) {
Oid ntype = lfirst_oid(lct2);
int32 ntypmod = lfirst_int(lcm2);
/*
* In the first iteration, ntype and ptype is the same element,
* but we ignore it as it's not a big problem here.
*/
if (!(ntype == ptype && ntypmod == ptypmod)) {
/* if any is different, false */
allsame = false;
}
/*
* MPP-15619 - backwards compatibility with existing view definitions.
*
* Historically we would cast UNKNOWN to text for most union queries,
* but there are many union cases where this historical behavior
* resulted in unacceptable errors (MPP-11377).
* To handle this we added additional code to resolve to a
* consistent cast for unions, which is generally better and
* handles more cases. However, in order to deal with backwards
* compatibility we have to deliberately hamstring this code and
* cast UNKNOWN to text if the other colums are STRING_TYPE
* even when some other datatype (such as name) might actually
* be more natural. This captures the set of views that
* we previously supported prior to the fix for MPP-11377 and
* thus is the set of views that we must not treat differently.
* This might be removed when we are ready to change view definition.
*/
if (ntype != UNKNOWNOID &&
STRING_TYPE != TypeCategory(getBaseType(ntype)))
hasnontext = true;
}
/*
* Backward compatibility; Unfortunately, we cannot change
* the old behavior of the part which was working without ERROR,
* mostly for the view definition. See comments above for detail.
* Setting InvalidOid for this column, the column type resolution
* will be falling back to the old process.
*/
if (!hasnontext) {
restype = InvalidOid;
restypmod = -1;
} else {
/*
* Even if the types are all the same, we resolve the type
* by select_common_type(), which casts domains to base types.
* Ideally, the domain types should be preserved, but to keep
* compatibility with older GPDB views, currently we don't change it.
* This restriction will be solved once upgrade/view issues get clean.
* See MPP-7509 for the issue.
*/
restype = select_common_type(types, context);
/*
* If there's no common type, the last resort is TEXT.
* See also select_common_type().
*/
if (restype == UNKNOWNOID) {
restype = TEXTOID;
restypmod = -1;
} else {
/*
* Essentially we preserve typmod only when all elements
* are identical, otherwise default (-1).
*/
if (allsame)
restypmod = ptypmod;
else
restypmod = -1;
}
}
pstate->p_setopTypes = lappend_oid(pstate->p_setopTypes, restype);
pstate->p_setopTypmods = lappend_int(pstate->p_setopTypmods, restypmod);
}
/*
* Recursively transform the components of the tree.
*/
sostmt = (SetOperationStmt *)transformSetOperationTree(pstate, stmt);
Assert(sostmt && IsA(sostmt, SetOperationStmt));
qry->setOperations = (Node *)sostmt;
/*
* Re-find leftmost SELECT (now it's a sub-query in rangetable)
*/
node = sostmt->larg;
while (node && IsA(node, SetOperationStmt))
node = ((SetOperationStmt *)node)->larg;
Assert(node && IsA(node, RangeTblRef));
leftmostRTI = ((RangeTblRef *)node)->rtindex;
leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
Assert(leftmostQuery != NULL);
/* Copy transformed distribution policy to query */
if (qry->intoClause) qry->intoPolicy = leftmostQuery->intoPolicy;
/*
* Generate dummy targetlist for outer query using column names of
* leftmost select and common datatypes of topmost set operation. Also
* make lists of the dummy vars and their names for use in parsing ORDER
* BY.
*
* Note: we use leftmostRTI as the varno of the dummy variables. It
* shouldn't matter too much which RT index they have, as long as they
* have one that corresponds to a real RT entry; else funny things may
* happen when the tree is mashed by rule rewriting.
*/
qry->targetList = NIL;
targetvars = NIL;
targetnames = NIL;
left_tlist = list_head(leftmostQuery->targetList);
forboth(lct, sostmt->colTypes, lcm, sostmt->colTypmods) {
Oid colType = lfirst_oid(lct);
int32 colTypmod = lfirst_int(lcm);
TargetEntry *lefttle = (TargetEntry *)lfirst(left_tlist);
char *colName;
TargetEntry *tle;
Expr *expr;
Assert(!lefttle->resjunk);
colName = pstrdup(lefttle->resname);
expr = (Expr *)makeVar(leftmostRTI, lefttle->resno, colType, colTypmod, 0);
tle = makeTargetEntry(expr, (AttrNumber)pstate->p_next_resno++, colName,
false);
qry->targetList = lappend(qry->targetList, tle);
targetvars = lappend(targetvars, expr);
targetnames = lappend(targetnames, makeString(colName));
left_tlist = lnext(left_tlist);
}
/*
* Coerce the UNKNOWN type for target entries to its right type here.
*/
fixup_unknown_vars_in_setop(pstate, sostmt);
/*
* As a first step towards supporting sort clauses that are expressions
* using the output columns, generate a varnamespace entry that makes the
* output columns visible. A Join RTE node is handy for this, since we
* can easily control the Vars generated upon matches.
*
* Note: we don't yet do anything useful with such cases, but at least
* "ORDER BY upper(foo)" will draw the right error message rather than
* "foo not found".
*/
jrte = addRangeTableEntryForJoin(NULL, targetnames, JOIN_INNER, targetvars,
NULL, false);
sv_rtable = pstate->p_rtable;
pstate->p_rtable = list_make1(jrte);
sv_relnamespace = pstate->p_relnamespace;
pstate->p_relnamespace = NIL; /* no qualified names allowed */
sv_varnamespace = pstate->p_varnamespace;
pstate->p_varnamespace = list_make1(jrte);
/*
* For now, we don't support resjunk sort clauses on the output of a
* setOperation tree --- you can only use the SQL92-spec options of
* selecting an output column by name or number. Enforce by checking that
* transformSortClause doesn't add any items to tlist.
*/
tllen = list_length(qry->targetList);
qry->sortClause = transformSortClause(pstate, sortClause, &qry->targetList,
false /* no unknowns expected */,
false /* use SQL92 rules */);
pstate->p_rtable = sv_rtable;
pstate->p_relnamespace = sv_relnamespace;
pstate->p_varnamespace = sv_varnamespace;
if (tllen != list_length(qry->targetList))
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"ORDER BY on a UNION/INTERSECT/EXCEPT result must be "
"on one of the result columns"),
errOmitLocation(true)));
qry->limitOffset = transformLimitClause(pstate, limitOffset, "OFFSET");
qry->limitCount = transformLimitClause(pstate, limitCount, "LIMIT");
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
/*
* Handle SELECT INTO/CREATE TABLE AS.
*
* Any column names from CREATE TABLE AS need to be attached to both the
* top level and the leftmost subquery. We do not do this earlier because
* we do *not* want sortClause processing to be affected.
*/
if (intoColNames) {
applyColumnNames(qry->targetList, intoColNames);
applyColumnNames(leftmostQuery->targetList, intoColNames);
}
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
parseCheckAggregates(pstate, qry);
if (pstate->p_hasTblValueExpr) parseCheckTableFunctions(pstate, qry);
foreach (l, lockingClause) {
transformLockingClause(qry, (LockingClause *)lfirst(l));
}
return qry;
}
/*
* transformSetOperationTree
* Recursively transform leaves and internal nodes of a set-op tree
*/
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) {
Assert(stmt && IsA(stmt, SelectStmt));
/*
* Validity-check both leaf and internal SELECTs for disallowed ops.
*/
if (stmt->intoClause)
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
errOmitLocation(true)));
/* We don't support FOR UPDATE/SHARE with set ops at the moment. */
if (stmt->lockingClause)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with "
"UNION/INTERSECT/EXCEPT"),
errOmitLocation(true)));
if (isSetopLeaf(stmt)) {
/* Process leaf SELECT */
List *selectList;
Query *selectQuery;
char selectName[32];
RangeTblEntry *rte;
RangeTblRef *rtr;
/*
* Transform SelectStmt into a Query.
*
* Note: previously transformed sub-queries don't affect the parsing
* of this sub-query, because they are not in the toplevel pstate's
* namespace list.
*/
selectList = parse_sub_analyze((Node *)stmt, pstate);
Assert(list_length(selectList) == 1);
selectQuery = (Query *)linitial(selectList);
Assert(IsA(selectQuery, Query));
/*
* Check for bogus references to Vars on the current query level (but
* upper-level references are okay). Normally this can't happen
* because the namespace will be empty, but it could happen if we are
* inside a rule.
*/
if (pstate->p_relnamespace || pstate->p_varnamespace) {
if (contain_vars_of_level((Node *)selectQuery, 1))
ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg(
"UNION/INTERSECT/EXCEPT member statement may not "
"refer to other relations of same query level"),
errOmitLocation(true)));
}
/*
* Make the leaf query be a subquery in the top-level rangetable.
*/
snprintf(selectName, sizeof(selectName), "*SELECT* %d",
list_length(pstate->p_rtable) + 1);
rte = addRangeTableEntryForSubquery(pstate, selectQuery,
makeAlias(selectName, NIL), false);
/*
* Return a RangeTblRef to replace the SelectStmt in the set-op tree.
*/
rtr = makeNode(RangeTblRef);
/* assume new rte is at end */
rtr->rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
return (Node *)rtr;
} else {
/* Process an internal node (set operation node) */
SetOperationStmt *op = makeNode(SetOperationStmt);
List *lcoltypes;
List *rcoltypes;
List *lcoltypmods;
List *rcoltypmods;
ListCell *lct;
ListCell *rct;
ListCell *mct;
ListCell *lcm;
ListCell *rcm;
ListCell *mcm;
const char *context;
context = (stmt->op == SETOP_UNION
? "UNION"
: (stmt->op == SETOP_INTERSECT ? "INTERSECT" : "EXCEPT"));
op->op = stmt->op;
op->all = stmt->all;
/*
* Recursively transform the child nodes.
*/
op->larg = transformSetOperationTree(pstate, stmt->larg);
op->rarg = transformSetOperationTree(pstate, stmt->rarg);
/*
* Verify that the two children have the same number of non-junk
* columns, and determine the types of the merged output columns.
* At one time in past, this information was used to deduce
* common data type, but now we don't; predict it beforehand,
* since in some cases transformation of individual leaf query
* hides what type the column should be from the whole of tree view.
*/
getSetColTypes(pstate, op->larg, &lcoltypes, &lcoltypmods);
getSetColTypes(pstate, op->rarg, &rcoltypes, &rcoltypmods);
if (list_length(lcoltypes) != list_length(rcoltypes))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("each %s query must have the same number of columns",
context),
errOmitLocation(true)));
Assert(list_length(lcoltypes) == list_length(lcoltypmods));
Assert(list_length(rcoltypes) == list_length(rcoltypmods));
op->colTypes = NIL;
op->colTypmods = NIL;
/* We should have predicted types and typmods up to now */
Assert(pstate->p_setopTypes && pstate->p_setopTypmods);
Assert(list_length(pstate->p_setopTypes) ==
list_length(pstate->p_setopTypmods));
Assert(list_length(pstate->p_setopTypes) == list_length(lcoltypes));
/* Iterate each column with tree candidates */
lct = list_head(lcoltypes);
rct = list_head(rcoltypes);
lcm = list_head(lcoltypmods);
rcm = list_head(rcoltypmods);
forboth(mct, pstate->p_setopTypes, mcm, pstate->p_setopTypmods) {
Oid lcoltype = lfirst_oid(lct);
Oid rcoltype = lfirst_oid(rct);
int32 lcoltypmod = lfirst_int(lcm);
int32 rcoltypmod = lfirst_int(rcm);
Oid rescoltype = lfirst_oid(mct);
int32 rescoltypmod = lfirst_int(mcm);
/*
* If the preprocessed coltype is InvalidOid, we fall back
* to the old style type resolution for backward
* compatibility. See transformSetOperationStmt for the reason.
*/
if (!OidIsValid(rescoltype)) {
/* select common type, same as CASE et al */
rescoltype =
select_common_type(list_make2_oid(lcoltype, rcoltype), context);
/* if same type and same typmod, use typmod; else default */
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
rescoltypmod = lcoltypmod;
}
/* Set final decision */
op->colTypes = lappend_oid(op->colTypes, rescoltype);
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
lct = lnext(lct);
lcm = lnext(lcm);
rct = lnext(rct);
rcm = lnext(rcm);
}
return (Node *)op;
}
}
/*
* isSetopLeaf
* returns true if the statement is set operation tree leaf.
*/
static bool isSetopLeaf(SelectStmt *stmt) {
Assert(stmt && IsA(stmt, SelectStmt));
/*
* If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
* clauses attached, we need to treat it like a leaf node to generate an
* independent sub-Query tree. Otherwise, it can be represented by a
* SetOperationStmt node underneath the parent Query.
*/
if (stmt->op == SETOP_NONE) {
Assert(stmt->larg == NULL && stmt->rarg == NULL);
return true;
} else {
Assert(stmt->larg != NULL && stmt->rarg != NULL);
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
stmt->lockingClause)
return true;
else
return false;
}
}
/*
* collectSetopTypes
* transforms the statement partially and collect data type oid
* and typmod from targetlist recursively. In set operations, the
* final data type should be determined from the total tree view,
* so we traverse the tree and collect types naively (without coercing)
* and use the information for later column type decision.
* types and typmods are output parameter, and the returned values
* are List of List which contain column number of elements as the
* first dimension, and leaf number of elements as the second dimension.
*/
static void collectSetopTypes(ParseState *pstate, SelectStmt *stmt,
List **types, List **typmods) {
if (isSetopLeaf(stmt)) {
ParseState *parentstate = pstate;
SelectStmt *select_stmt = stmt;
List *tlist;
ListCell *lc, *lct, *lcm;
/* Copy them just in case */
pstate = make_parsestate(parentstate);
stmt = copyObject(select_stmt);
if (stmt->valuesLists) {
/* in VALUES query, we can transform all */
tlist = transformValuesClause(pstate, stmt)->targetList;
} else {
/* transform only tragetList */
transformFromClause(pstate, stmt->fromClause);
tlist = transformTargetList(pstate, stmt->targetList);
}
if (*types == NIL) {
Assert(*typmods == NIL);
/* Construct List of List for numbers of tlist */
foreach (lc, tlist) {
*types = lappend(*types, NIL);
*typmods = lappend(*typmods, NIL);
}
} else if (list_length(*types) != list_length(tlist)) {
/*
* Must be an error in later process.
* Nothing to do in this preprocess (not an assert.)
*/
free_parsestate(&pstate);
pfree(stmt);
return;
}
lct = list_head(*types);
lcm = list_head(*typmods);
foreach (lc, tlist) {
TargetEntry *tle = (TargetEntry *)lfirst(lc);
List *typelist = (List *)lfirst(lct);
List *typmodlist = (List *)lfirst(lcm);
/* Keep back to the original List */
lfirst(lct) = lappend_oid(typelist, exprType((Node *)tle->expr));
lfirst(lcm) = lappend_int(typmodlist, exprTypmod((Node *)tle->expr));
lct = lnext(lct);
lcm = lnext(lcm);
}
/* They're not needed anymore */
free_parsestate(&pstate);
pfree(stmt);
} else {
/* just recurse to the leaf */
collectSetopTypes(pstate, stmt->larg, types, typmods);
collectSetopTypes(pstate, stmt->rarg, types, typmods);
}
}
/*
* getSetColTypes
* Get output column types/typmods of an (already transformed) set-op
*node
*/
static void getSetColTypes(ParseState *pstate, Node *node, List **colTypes,
List **colTypmods) {
*colTypes = NIL;
*colTypmods = NIL;
if (IsA(node, RangeTblRef)) {
RangeTblRef *rtr = (RangeTblRef *)node;
RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
Query *selectQuery = rte->subquery;
ListCell *tl;
Assert(selectQuery != NULL);
/* Get types of non-junk columns */
foreach (tl, selectQuery->targetList) {
TargetEntry *tle = (TargetEntry *)lfirst(tl);
if (tle->resjunk) continue;
*colTypes = lappend_oid(*colTypes, exprType((Node *)tle->expr));
*colTypmods = lappend_int(*colTypmods, exprTypmod((Node *)tle->expr));
}
} else if (IsA(node, SetOperationStmt)) {
SetOperationStmt *op = (SetOperationStmt *)node;
/* Result already computed during transformation of node */
Assert(op->colTypes != NIL);
*colTypes = op->colTypes;
*colTypmods = op->colTypmods;
} else
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(node));
}
/* Attach column names from a ColumnDef list to a TargetEntry list */
static void applyColumnNames(List *dst, List *src) {
ListCell *dst_item;
ListCell *src_item;
src_item = list_head(src);
foreach (dst_item, dst) {
TargetEntry *d = (TargetEntry *)lfirst(dst_item);
ColumnDef *s;
/* junk targets don't count */
if (d->resjunk) continue;
/* fewer ColumnDefs than target entries is OK */
if (src_item == NULL) break;
s = (ColumnDef *)lfirst(src_item);
src_item = lnext(src_item);
d->resname = pstrdup(s->colname);
}
/* more ColumnDefs than target entries is not OK */
if (src_item != NULL)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE TABLE AS specifies too many column names"),
errOmitLocation(true)));
}
/*
* transformUpdateStmt -
* transforms an update statement
*/
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) {
Query *qry = makeNode(Query);
Node *qual;
ListCell *origTargetList;
ListCell *tl;
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
/* setup database name for use of magma operations */
MemoryContext oldContext = MemoryContextSwitchTo(MessageContext);
int isGraph = parseAndTransformAsGraph(pstate, stmt->relation);
char *dbname = stmt->relation->catalogname;
database =
(dbname != NULL) ? pstrdup(dbname) : get_database_name(MyDatabaseId);
MemoryContextSwitchTo(oldContext);
qry->resultRelation = setTargetTable(
pstate, stmt->relation, interpretInhOption(stmt->relation->inhOpt), true,
ACL_UPDATE);
if(isGraph)
pstate->p_target_rangetblentry->graphName = stmt->relation->schemaname ?
pstrdup(stmt->relation->schemaname) : NULL;
/*
* the FROM clause is non-standard SQL syntax. We used to be able to do
* this with REPLACE in POSTQUEL so we keep the feature.
*/
transformFromClause(pstate, stmt->fromClause);
qry->targetList = transformTargetList(pstate, stmt->targetList);
qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
/*
* MPP-2506 [insert/update/delete] RETURNING clause not supported:
* We have problems processing the returning clause, so for now we have
* simply removed it and replaced it with an error message.
*/
#ifdef MPP_RETURNING_NOT_SUPPORTED
if (stmt->returningList) {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"The RETURNING clause of the UPDATE statement is not "
"supported in this version of Greenplum Database."),
errOmitLocation(true)));
}
#else
qry->returningList = transformReturningList(pstate, stmt->returningList);
#endif
/*
* CDB: Untyped Const or Param nodes in a subquery in the FROM clause
* could have been assigned proper types when we transformed the WHERE
* clause or targetlist above. Bring targetlist Var types up to date.
*/
if (stmt->fromClause) {
fixup_unknown_vars_in_targetlist(pstate, qry->targetList);
fixup_unknown_vars_in_targetlist(pstate, qry->returningList);
}
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
/*
* Top-level aggregates are simply disallowed in UPDATE, per spec. (From
* an implementation point of view, this is forced because the implicit
* ctid reference would otherwise be an ungrouped variable.)
*/
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in UPDATE"),
errOmitLocation(true)));
if (pstate->p_hasWindFuncs)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in UPDATE"),
errOmitLocation(true)));
/*
* Now we are done with SELECT-like processing, and can get on with
* transforming the target list to match the UPDATE target columns.
*/
/* Prepare to assign non-conflicting resnos to resjunk attributes */
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
/* Prepare non-junk columns for assignment to target table */
origTargetList = list_head(stmt->targetList);
int pknatts = 0;
AttrNumber *pkattno = NULL;
/* get primary keys for check not supported update pkey column */
if (RelationIsExternal(pstate->p_target_relation)) {
Oid reloid = RelationGetRelid(pstate->p_target_relation);
ExtTableEntry *extentry = GetExtTableEntry(reloid);
char *formatter = getExtTblFormatterTypeInFmtOptsStr(extentry->fmtopts);
if (formatter && pg_strncasecmp(formatter, "magma", strlen("magma")) == 0) {
ConstraintGetPrimaryKeys(reloid, &pknatts, &pkattno);
}
}
foreach (tl, qry->targetList) {
TargetEntry *tle = (TargetEntry *)lfirst(tl);
ResTarget *origTarget;
int attrno;
if (tle->resjunk) {
/*
* Resjunk nodes need no additional processing, but be sure they
* have resnos that do not match any target columns; else rewriter
* or planner might get confused. They don't need a resname
* either.
*/
tle->resno = (AttrNumber)pstate->p_next_resno++;
tle->resname = NULL;
continue;
}
if (origTargetList == NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
origTarget = (ResTarget *)lfirst(origTargetList);
Assert(IsA(origTarget, ResTarget));
/* CDB: Drop a breadcrumb in case of error. */
pstate->p_breadcrumb.node = (Node *)origTarget;
attrno = attnameAttNum(pstate->p_target_relation, origTarget->name, true);
if (attrno == InvalidAttrNumber)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
origTarget->name, RelationGetRelationName(
pstate->p_target_relation)),
errOmitLocation(true),
parser_errposition(pstate, origTarget->location)));
/*
* if there are primary keys, then check whether the targetlist contains
* primary keys, cannot update primary key columns.
*/
if (pknatts >= 1) {
for (int j = 0; j < pknatts; ++j) {
if (pkattno[j] == attrno) {
ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg(
"cannot update primary key column \"%s\""
" of relation \"%s\" in \"magmatp or magmaap\" format",
origTarget->name, RelationGetRelationName(
pstate->p_target_relation)),
errOmitLocation(true),
parser_errposition(pstate, origTarget->location)));
} else {
continue;
}
}
}
updateTargetListEntry(pstate, tle, origTarget->name, attrno,
origTarget->indirection, origTarget->location);
origTargetList = lnext(origTargetList);
}
if (origTargetList != NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
return qry;
}
/*
* MPP-2506 [insert/update/delete] RETURNING clause not supported:
* We have problems processing the returning clause, so for now we have
* simply removed it and replaced it with an error message.
*/
#ifndef MPP_RETURNING_NOT_SUPPORTED
/*
* transformReturningList -
* handle a RETURNING clause in INSERT/UPDATE/DELETE
*/
static List *transformReturningList(ParseState *pstate, List *returningList) {
List *rlist;
int save_next_resno;
bool save_hasAggs;
int length_rtable;
if (returningList == NIL) return NIL; /* nothing to do */
/*
* We need to assign resnos starting at one in the RETURNING list. Save
* and restore the main tlist's value of p_next_resno, just in case
* someone looks at it later (probably won't happen).
*/
save_next_resno = pstate->p_next_resno;
pstate->p_next_resno = 1;
/* save other state so that we can detect disallowed stuff */
save_hasAggs = pstate->p_hasAggs;
pstate->p_hasAggs = false;
length_rtable = list_length(pstate->p_rtable);
/* transform RETURNING identically to a SELECT targetlist */
rlist = transformTargetList(pstate, returningList);
/* CDB: Cursor position not available for errors below this point. */
pstate->p_breadcrumb.node = NULL;
/* check for disallowed stuff */
/* aggregates not allowed (but subselects are okay) */
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in RETURNING")));
if (pstate->p_hasWindFuncs)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in RETURNING")));
/* no new relation references please */
if (list_length(pstate->p_rtable) != length_rtable)
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("RETURNING may not contain references to other relations")));
/* mark column origins */
markTargetListOrigins(pstate, rlist);
/* restore state */
pstate->p_next_resno = save_next_resno;
pstate->p_hasAggs = save_hasAggs;
return rlist;
}
#endif
/*
* transformAlterTable_all_PartitionStmt -
* transform an Alter Table Statement for some Partition operation
*/
static AlterTableCmd *transformAlterTable_all_PartitionStmt(
ParseState *pstate, AlterTableStmt *stmt, CreateStmtContext *pCxt,
AlterTableCmd *cmd, List **extras_before, List **extras_after) {
AlterPartitionCmd *pc = (AlterPartitionCmd *)cmd->def;
AlterPartitionCmd *pci = pc;
AlterPartitionId *pid = (AlterPartitionId *)pci->partid;
AlterTableCmd *atc1 = cmd;
RangeVar *rv = stmt->relation;
PartitionNode *pNode = NULL;
PartitionNode *prevNode = NULL;
int partDepth = 0;
Oid par_oid = InvalidOid;
StringInfoData sid1, sid2;
stmt->greenWay = true;
if (atc1->subtype == AT_PartAlter) {
PgPartRule *prule = NULL;
char *lrelname;
Relation rel = heap_openrv(rv, AccessShareLock);
initStringInfo(&sid1);
initStringInfo(&sid2);
appendStringInfo(&sid1, "relation \"%s\"", RelationGetRelationName(rel));
lrelname = sid1.data;
pNode = RelationBuildPartitionDesc(rel, false);
if (!pNode)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("%s is not partitioned", lrelname)));
/* Processes nested ALTER (if it exists) */
while (1) {
AlterPartitionId *pid2 = NULL;
if (atc1->subtype != AT_PartAlter) {
rv = makeRangeVar(NULL /*catalogname*/,
get_namespace_name(RelationGetNamespace(rel)),
pstrdup(RelationGetRelationName(rel)), -1);
heap_close(rel, AccessShareLock);
rel = NULL;
break;
}
pid2 = (AlterPartitionId *)pci->partid;
if (pid2 && (pid2->idtype == AT_AP_IDValue)) {
List *vallist = (List *)pid2->partiddef;
pid2->partiddef = (Node *)transformExpressionList(pstate, vallist);
}
partDepth++;
if (!pNode)
prule = NULL;
else
prule = get_part_rule1(rel, pid2, false, true, CurrentMemoryContext,
NULL, pNode, sid1.data, NULL);
if (prule && prule->topRule && prule->topRule->children) {
prevNode = pNode;
pNode = prule->topRule->children;
par_oid = RelationGetRelid(rel);
/*
* Don't hold a long lock -- lock on the master is
* sufficient
*/
heap_close(rel, AccessShareLock);
rel = heap_open(prule->topRule->parchildrelid, AccessShareLock);
appendStringInfo(&sid2, "partition%s of %s", prule->partIdStr,
sid1.data);
truncateStringInfo(&sid1, 0);
appendStringInfo(&sid1, "%s", sid2.data);
truncateStringInfo(&sid2, 0);
} else {
prevNode = pNode;
pNode = NULL;
}
atc1 = (AlterTableCmd *)pci->arg1;
pci = (AlterPartitionCmd *)atc1->def;
} /* end while */
if (rel) /* No need to hold onto the lock -- see above */
heap_close(rel, AccessShareLock);
} /* end if alter */
switch (atc1->subtype) {
case AT_PartAdd: /* Add */
case AT_PartSetTemplate: /* Set Subpartn Template */
if (pci->arg2) /* could be null for settemplate... */
{
AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pci->arg2;
InhRelation *inh = makeNode(InhRelation);
List *cl = NIL;
Assert(IsA(pc2->arg2, List));
inh->relation = copyObject(rv);
inh->options = list_make3_int(CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
CREATE_TABLE_LIKE_INCLUDING_INDEXES);
/*
* fill in remaining fields from parse time (gram.y):
* the new partition is LIKE the parent and it
* inherits from it
*/
if (IsA(linitial((List *)pc2->arg2), CreateExternalStmt)) {
CreateExternalStmt *ct;
ct = (CreateExternalStmt *)linitial((List *)pc2->arg2);
ct->base.tableElts = lappend(ct->base.tableElts, inh);
cl = list_make1(ct);
} else {
CreateStmt *ct;
ct = (CreateStmt *)linitial((List *)pc2->arg2);
ct->base.tableElts = lappend(ct->base.tableElts, inh);
cl = list_make1(ct);
}
pc2->arg2 = (Node *)cl;
}
case AT_PartCoalesce: /* Coalesce */
case AT_PartDrop: /* Drop */
case AT_PartExchange: /* Exchange */
case AT_PartMerge: /* Merge */
case AT_PartModify: /* Modify */
case AT_PartRename: /* Rename */
case AT_PartTruncate: /* Truncate */
case AT_PartSplit: /* Split */
/* MPP-4011: get right pid for FOR(value) */
pid = (AlterPartitionId *)pci->partid;
if (pid && (pid->idtype == AT_AP_IDValue)) {
List *vallist = (List *)pid->partiddef;
pid->partiddef = (Node *)transformExpressionList(pstate, vallist);
}
break;
default:
break;
}
/* transform boundary specifications at execute time */
return cmd;
} /* end transformAlterTable_all_PartitionStmt */
/*
* transformAlterTableStmt -
* transform an Alter Table Statement
*/
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
List **extras_before,
List **extras_after) {
CreateStmtContext cxt;
Query *qry;
ListCell *lcmd, *l;
List *newcmds = NIL;
bool skipValidation = true;
AlterTableCmd *newcmd;
cxt.stmtType = "ALTER TABLE";
cxt.isExternalTable = false;
cxt.relation = stmt->relation;
cxt.inhRelations = NIL;
cxt.isalter = true;
cxt.hasoids = false; /* need not be right */
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
cxt.dlist = NIL; /* used by transformCreateStmt, not here */
cxt.pkey = NULL;
/*
* The only subtypes that currently require parse transformation handling
* are ADD COLUMN and ADD CONSTRAINT. These largely re-use code from
* CREATE TABLE.
* And ALTER TABLE ... <operator> PARTITION ...
*/
foreach (lcmd, stmt->cmds) {
AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lcmd);
switch (cmd->subtype) {
case AT_AddColumn: {
ColumnDef *def = (ColumnDef *)cmd->def;
Assert(IsA(cmd->def, ColumnDef));
/*
* Disallow adding a column with primary key constraint
*/
if (Gp_role == GP_ROLE_DISPATCH) {
ListCell *c;
foreach (c, def->constraints) {
Constraint *cons = (Constraint *)lfirst(c);
if (cons->contype == CONSTR_PRIMARY)
elog(ERROR,
"Cannot add column with primary "
"key constraint");
if (cons->contype == CONSTR_UNIQUE)
elog(ERROR,
"Cannot add column with unique "
"constraint");
}
}
transformColumnDefinition(pstate, &cxt, (ColumnDef *)cmd->def);
/*
* If the column has a non-null default, we can't skip
* validation of foreign keys.
*/
if (((ColumnDef *)cmd->def)->raw_default != NULL)
skipValidation = false;
newcmds = lappend(newcmds, cmd);
/*
* Convert an ADD COLUMN ... NOT NULL constraint to a
* separate command
*/
if (def->is_not_null) {
/* Remove NOT NULL from AddColumn */
def->is_not_null = false;
/* Add as a separate AlterTableCmd */
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_SetNotNull;
newcmd->name = pstrdup(def->colname);
newcmds = lappend(newcmds, newcmd);
}
/*
* All constraints are processed in other ways. Remove the
* original list
*/
def->constraints = NIL;
break;
}
case AT_AddConstraint:
/*
* The original AddConstraint cmd node doesn't go to newcmds
*/
if (IsA(cmd->def, Constraint))
transformTableConstraint(pstate, &cxt, (Constraint *)cmd->def);
else if (IsA(cmd->def, FkConstraint)) {
cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def);
/* GPDB: always skip validation of foreign keys */
skipValidation = true;
} else
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(cmd->def));
break;
case AT_ProcessedConstraint:
/*
* Already-transformed ADD CONSTRAINT, so just make it look
* like the standard case.
*/
cmd->subtype = AT_AddConstraint;
newcmds = lappend(newcmds, cmd);
break;
/* CDB: Partitioned Tables */
case AT_PartAlter: /* Alter */
case AT_PartAdd: /* Add */
case AT_PartCoalesce: /* Coalesce */
case AT_PartDrop: /* Drop */
case AT_PartExchange: /* Exchange */
case AT_PartMerge: /* Merge */
case AT_PartModify: /* Modify */
case AT_PartRename: /* Rename */
case AT_PartSetTemplate: /* Set Subpartition Template */
case AT_PartSplit: /* Split */
case AT_PartTruncate: /* Truncate */
{
cmd = transformAlterTable_all_PartitionStmt(
pstate, stmt, &cxt, cmd, extras_before, extras_after);
newcmds = lappend(newcmds, cmd);
break;
}
default:
newcmds = lappend(newcmds, cmd);
break;
}
}
/*
* transformIndexConstraints wants cxt.alist to contain only index
* statements, so transfer anything we already have into extras_after
* immediately.
*/
*extras_after = list_concat(cxt.alist, *extras_after);
cxt.alist = NIL;
/* Postprocess index and FK constraints */
transformIndexConstraints(pstate, &cxt, false);
transformFKConstraints(pstate, &cxt, skipValidation, true);
/*
* Push any index-creation commands into the ALTER, so that they can be
* scheduled nicely by tablecmds.c.
*/
foreach (l, cxt.alist) {
Node *idxstmt = (Node *)lfirst(l);
Assert(IsA(idxstmt, IndexStmt));
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddIndex;
newcmd->def = idxstmt;
newcmds = lappend(newcmds, newcmd);
}
cxt.alist = NIL;
/* Append any CHECK or FK constraints to the commands list */
foreach (l, cxt.ckconstraints) {
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
newcmd->def = (Node *)lfirst(l);
newcmds = lappend(newcmds, newcmd);
}
foreach (l, cxt.fkconstraints) {
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
newcmd->def = (Node *)lfirst(l);
newcmds = lappend(newcmds, newcmd);
}
/* Update statement's commands list */
stmt->cmds = newcmds;
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
qry->utilityStmt = (Node *)stmt;
*extras_before = list_concat(*extras_before, cxt.blist);
*extras_after = list_concat(cxt.alist, *extras_after);
return qry;
}
/*
* transformDeclareCursorStmt -
* transform a DECLARE CURSOR Statement
*
* DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not
* significantly different from a SELECT) as far as parsing/rewriting/planning
* are concerned, but it's not passed to the executor and so in that sense is
* a utility statement. We transform it into a Query exactly as if it were
* a SELECT, then stick the original DeclareCursorStmt into the utilityStmt
* field to carry the cursor name and options.
*/
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt) {
Query *result = makeNode(Query);
List *extras_before = NIL, *extras_after = NIL;
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *)stmt;
/*
* Don't allow both SCROLL and NO SCROLL to be specified
*/
if ((stmt->options & CURSOR_OPT_SCROLL) &&
(stmt->options & CURSOR_OPT_NO_SCROLL))
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot specify both SCROLL and NO SCROLL")));
result = transformStmt(pstate, stmt->query, &extras_before, &extras_after);
/* Shouldn't get any extras, since grammar only allows SelectStmt */
if (extras_before || extras_after)
elog(ERROR, "unexpected extra stuff in cursor statement");
/* Grammar should not have allowed anything but SELECT */
if (!IsA(result, Query) || result->commandType != CMD_SELECT ||
result->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (result->intoClause)
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR cannot specify INTO")));
/* We won't need the raw querytree any more */
stmt->query = NULL;
stmt->is_simply_updatable = isSimplyUpdatableQuery(result);
result->utilityStmt = (Node *)stmt;
return result;
}
/*
* isSimplyUpdatableQuery -
* determine whether a query is a simply updatable scan of a relation
*
* A query is simply updatable if, and only if, it...
* - has no window clauses
* - has no sort clauses
* - has no grouping, having, distinct clauses, or simple aggregates
* - has no subqueries
* - has no LIMIT/OFFSET
* - references only one range table (i.e. no joins, self-joins)
* - this range table must itself be updatable
*
*/
static bool isSimplyUpdatableQuery(Query *query) {
Assert(query->commandType == CMD_SELECT);
if (query->windowClause == NIL && query->sortClause == NIL &&
query->groupClause == NIL && query->havingQual == NULL &&
query->distinctClause == NIL && !query->hasAggs && !query->hasSubLinks &&
query->limitCount == NULL && query->limitOffset == NULL &&
list_length(query->rtable) == 1) {
RangeTblEntry *rte = (RangeTblEntry *)linitial(query->rtable);
if (isSimplyUpdatableRelation(rte->relid)) return true;
}
return false;
}
static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt) {
Query *result = makeNode(Query);
List *argtype_oids; /* argtype OIDs in a list */
Oid *argtoids = NULL; /* and as an array */
int nargs;
List *queries;
int i;
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *)stmt;
/* Transform list of TypeNames to list (and array) of type OIDs */
nargs = list_length(stmt->argtypes);
if (nargs) {
ListCell *l;
argtoids = (Oid *)palloc(nargs * sizeof(Oid));
i = 0;
foreach (l, stmt->argtypes) {
TypeName *tn = lfirst(l);
Oid toid = typenameTypeId(pstate, tn);
/* Pseudotypes are not valid parameters to PREPARE */
if (get_typtype(toid) == TYPTYPE_PSEUDO) {
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("type \"%s\" is not a valid parameter for PREPARE",
TypeNameToString(tn))));
}
argtoids[i++] = toid;
}
}
/*
* Analyze the statement using these parameter types (any parameters
* passed in from above us will not be visible to it), allowing
* information about unknown parameters to be deduced from context.
*/
queries = parse_analyze_varparams((Node *)stmt->query, pstate->p_sourcetext,
&argtoids, &nargs);
/*
* Shouldn't get any extra statements, since grammar only allows
* OptimizableStmt
*/
if (list_length(queries) != 1)
elog(ERROR, "unexpected extra stuff in prepared statement");
/*
* Check that all parameter types were determined, and convert the array
* of OIDs into a list for storage.
*/
argtype_oids = NIL;
for (i = 0; i < nargs; i++) {
Oid argtype = argtoids[i];
if (argtype == InvalidOid || argtype == UNKNOWNOID)
ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("could not determine data type of parameter $%d",
i + 1)));
argtype_oids = lappend_oid(argtype_oids, argtype);
}
stmt->argtype_oids = argtype_oids;
stmt->query = linitial(queries);
return result;
}
static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) {
Query *result = makeNode(Query);
List *paramtypes;
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *)stmt;
paramtypes = FetchPreparedStatementParams(stmt->name);
if (stmt->params || paramtypes) {
int nparams = list_length(stmt->params);
int nexpected = list_length(paramtypes);
ListCell *l, *l2;
int i = 1;
if (nparams != nexpected)
ereport(
ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("wrong number of parameters for prepared statement \"%s\"",
stmt->name),
errdetail("Expected %d parameters but got %d.", nexpected, nparams),
errOmitLocation(true)));
forboth(l, stmt->params, l2, paramtypes) {
Node *expr = lfirst(l);
Oid expected_type_id = lfirst_oid(l2);
Oid given_type_id;
expr = transformExpr(pstate, expr);
/* Cannot contain subselects or aggregates */
if (pstate->p_hasSubLinks)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use subquery in EXECUTE parameter")));
if (pstate->p_hasAggs)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in EXECUTE parameter")));
if (pstate->p_hasWindFuncs)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use window function in EXECUTE parameter")));
given_type_id = exprType(expr);
expr = coerce_to_target_type(pstate, expr, given_type_id,
expected_type_id, -1, COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST, -1);
if (expr == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg(
"parameter $%d of type %s cannot be coerced to the "
"expected type %s",
i, format_type_be(given_type_id),
format_type_be(expected_type_id)),
errhint("You will need to rewrite or cast the expression."),
errOmitLocation(true)));
lfirst(l) = expr;
i++;
}
}
return result;
}
/* exported so planner can check again after rewriting, query pullup, etc */
void CheckSelectLocking(Query *qry) {
if (qry->setOperations)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with "
"UNION/INTERSECT/EXCEPT")));
if (qry->distinctClause != NIL)
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
if (qry->groupClause != NIL)
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
if (qry->havingQual != NULL)
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
if (qry->hasAggs)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with aggregate "
"functions")));
if (qry->hasWindFuncs)
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE is not allowed with window functions")));
}
/*
* Transform a FOR UPDATE/SHARE clause
*
* This basically involves replacing names by integer relids.
*
* NB: if you need to change this, see also markQueryForLocking()
* in rewriteHandler.c.
*/
static void transformLockingClause(Query *qry, LockingClause *lc) {
List *lockedRels = lc->lockedRels;
ListCell *l;
ListCell *rt;
Index i;
LockingClause *allrels;
/* disable select for update for gpsql */
if (lc->forUpdate) {
ereport(ERROR, (errcode(ERRCODE_CDB_FEATURE_NOT_YET),
errmsg("Cannot support select for update statement yet")));
}
CheckSelectLocking(qry);
/* make a clause we can pass down to subqueries to select all rels */
allrels = makeNode(LockingClause);
allrels->lockedRels = NIL; /* indicates all rels */
allrels->forUpdate = lc->forUpdate;
allrels->noWait = lc->noWait;
if (lockedRels == NIL) {
/* all regular tables used in query */
i = 0;
foreach (rt, qry->rtable) {
RangeTblEntry *rte = (RangeTblEntry *)lfirst(rt);
++i;
switch (rte->rtekind) {
case RTE_RELATION:
if (get_rel_relstorage(rte->relid) == RELSTORAGE_EXTERNAL)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied to "
"external tables")));
applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
* subquery's rels
*/
transformLockingClause(rte->subquery, allrels);
break;
default:
/* ignore JOIN, SPECIAL, FUNCTION RTEs */
break;
}
}
} else {
/* just the named tables */
foreach (l, lockedRels) {
char *relname = strVal(lfirst(l));
i = 0;
foreach (rt, qry->rtable) {
RangeTblEntry *rte = (RangeTblEntry *)lfirst(rt);
++i;
if (strcmp(rte->eref->aliasname, relname) == 0) {
switch (rte->rtekind) {
case RTE_RELATION:
if (get_rel_relstorage(rte->relid) == RELSTORAGE_EXTERNAL)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied "
"to external tables")));
applyLockingClause(qry, i, lc->forUpdate, lc->noWait);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
/*
* FOR UPDATE/SHARE of subquery is propagated to
* all of subquery's rels
*/
transformLockingClause(rte->subquery, allrels);
break;
case RTE_JOIN:
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied to a join")));
break;
case RTE_SPECIAL:
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied "
"to NEW or OLD")));
break;
case RTE_FUNCTION:
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied "
"to a function")));
break;
case RTE_TABLEFUNCTION:
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied "
"to a table function")));
break;
case RTE_VALUES:
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
break;
default:
elog(ERROR, "unrecognized RTE type: %d", (int)rte->rtekind);
break;
}
break; /* out of foreach loop */
}
}
if (rt == NULL)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
errmsg(
"relation \"%s\" in FOR UPDATE/SHARE clause not "
"found in FROM clause",
relname)));
}
}
}
/*
* Record locking info for a single rangetable item
*/
void applyLockingClause(Query *qry, Index rtindex, bool forUpdate,
bool noWait) {
RowMarkClause *rc;
/* Check for pre-existing entry for same rtindex */
if ((rc = get_rowmark(qry, rtindex)) != NULL) {
/*
* If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
* it as FOR UPDATE. (Reasonable, since you can't take both a shared
* and exclusive lock at the same time; it'll end up being exclusive
* anyway.)
*
* We also consider that NOWAIT wins if it's specified both ways. This
* is a bit more debatable but raising an error doesn't seem helpful.
* (Consider for instance SELECT FOR UPDATE NOWAIT from a view that
* internally contains a plain FOR UPDATE spec.)
*/
rc->forUpdate |= forUpdate;
rc->noWait |= noWait;
return;
}
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->noWait = noWait;
qry->rowMarks = lappend(qry->rowMarks, rc);
}
/*
* Preprocess a list of column constraint clauses
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
* NOTE: currently, attributes are only supported for FOREIGN KEY primary
* constraints, but someday they ought to be supported for other constraints.
*/
static void transformConstraintAttrs(List *constraintList) {
Node *lastprimarynode = NULL;
bool saw_deferrability = false;
bool saw_initially = false;
ListCell *clist;
foreach (clist, constraintList) {
Node *node = lfirst(clist);
if (!IsA(node, Constraint)) {
lastprimarynode = node;
/* reset flags for new primary node */
saw_deferrability = false;
saw_initially = false;
} else {
Constraint *con = (Constraint *)node;
switch (con->contype) {
case CONSTR_ATTR_DEFERRABLE:
if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
if (saw_deferrability)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"multiple DEFERRABLE/NOT DEFERRABLE clauses "
"not allowed")));
saw_deferrability = true;
((FkConstraint *)lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
if (saw_deferrability)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"multiple DEFERRABLE/NOT DEFERRABLE clauses "
"not allowed")));
saw_deferrability = true;
((FkConstraint *)lastprimarynode)->deferrable = false;
if (saw_initially && ((FkConstraint *)lastprimarynode)->initdeferred)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"constraint declared INITIALLY DEFERRED must "
"be DEFERRABLE")));
break;
case CONSTR_ATTR_DEFERRED:
if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
if (saw_initially)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"multiple INITIALLY IMMEDIATE/DEFERRED clauses "
"not allowed")));
saw_initially = true;
((FkConstraint *)lastprimarynode)->initdeferred = true;
/*
* If only INITIALLY DEFERRED appears, assume DEFERRABLE
*/
if (!saw_deferrability)
((FkConstraint *)lastprimarynode)->deferrable = true;
else if (!((FkConstraint *)lastprimarynode)->deferrable)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"constraint declared INITIALLY DEFERRED must "
"be DEFERRABLE")));
break;
case CONSTR_ATTR_IMMEDIATE:
if (lastprimarynode == NULL || !IsA(lastprimarynode, FkConstraint))
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
if (saw_initially)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg(
"multiple INITIALLY IMMEDIATE/DEFERRED clauses "
"not allowed")));
saw_initially = true;
((FkConstraint *)lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
/* reset flags for new primary node */
saw_deferrability = false;
saw_initially = false;
break;
}
}
}
}
/* Build a FromExpr node */
static FromExpr *makeFromExpr(List *fromlist, Node *quals) {
FromExpr *f = makeNode(FromExpr);
f->fromlist = fromlist;
f->quals = quals;
return f;
}
/*
* Special handling of type definition for a column
*/
static void transformColumnType(ParseState *pstate, ColumnDef *column) {
/*
* All we really need to do here is verify that the type is valid.
*/
Type ctype = typenameType(NULL, column->typname);
ReleaseType(ctype);
}
static void setSchemaName(char *context_schema, char **stmt_schema_name) {
if (*stmt_schema_name == NULL)
*stmt_schema_name = context_schema;
else if (strcmp(context_schema, *stmt_schema_name) != 0)
ereport(ERROR, (errcode(ERRCODE_INVALID_SCHEMA_DEFINITION),
errmsg(
"CREATE specifies a schema (%s) "
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
/*
* analyzeCreateSchemaStmt -
* analyzes the "create schema" statement
*
* Split the schema element list into individual commands and place
* them in the result list in an order such that there are no forward
* references (e.g. GRANT to a table created later in the list). Note
* that the logic we use for determining forward references is
* presently quite incomplete.
*
* SQL92 also allows constraints to make forward references, so thumb through
* the table columns and move forward references to a posterior alter-table
* command.
*
* The result is a list of parse nodes that still need to be analyzed ---
* but we can't analyze the later commands until we've executed the earlier
* ones, because of possible inter-object references.
*
* Note: Called from commands/schemacmds.c
*/
List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt) {
CreateSchemaStmtContext cxt;
List *result;
ListCell *elements;
cxt.stmtType = "CREATE SCHEMA";
cxt.schemaname = stmt->schemaname;
cxt.authid = stmt->authid;
cxt.sequences = NIL;
cxt.tables = NIL;
cxt.views = NIL;
cxt.indexes = NIL;
cxt.grants = NIL;
cxt.triggers = NIL;
cxt.fwconstraints = NIL;
cxt.alters = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
/*
* Run through each schema element in the schema element list. Separate
* statements by type, and do preliminary analysis.
*/
foreach (elements, stmt->schemaElts) {
Node *element = lfirst(elements);
switch (nodeTag(element)) {
case T_CreateSeqStmt: {
CreateSeqStmt *elp = (CreateSeqStmt *)element;
setSchemaName(cxt.schemaname, &elp->sequence->schemaname);
cxt.sequences = lappend(cxt.sequences, element);
} break;
case T_CreateStmt: {
CreateStmt *elp = (CreateStmt *)element;
setSchemaName(cxt.schemaname, &elp->base.relation->schemaname);
/*
* XXX todo: deal with constraints
*/
cxt.tables = lappend(cxt.tables, element);
} break;
case T_CreateExternalStmt: {
CreateExternalStmt *elp = (CreateExternalStmt *)element;
setSchemaName(cxt.schemaname, &elp->base.relation->schemaname);
cxt.tables = lappend(cxt.tables, element);
} break;
case T_CreateForeignStmt: {
CreateForeignStmt *elp = (CreateForeignStmt *)element;
setSchemaName(cxt.schemaname, &elp->relation->schemaname);
cxt.tables = lappend(cxt.tables, element);
} break;
case T_ViewStmt: {
ViewStmt *elp = (ViewStmt *)element;
setSchemaName(cxt.schemaname, &elp->view->schemaname);
/*
* XXX todo: deal with references between views
*/
cxt.views = lappend(cxt.views, element);
} break;
case T_IndexStmt: {
IndexStmt *elp = (IndexStmt *)element;
setSchemaName(cxt.schemaname, &elp->relation->schemaname);
cxt.indexes = lappend(cxt.indexes, element);
} break;
case T_CreateTrigStmt: {
CreateTrigStmt *elp = (CreateTrigStmt *)element;
setSchemaName(cxt.schemaname, &elp->relation->schemaname);
cxt.triggers = lappend(cxt.triggers, element);
} break;
case T_GrantStmt:
cxt.grants = lappend(cxt.grants, element);
break;
default:
elog(ERROR, "unrecognized node type: %d", (int)nodeTag(element));
}
}
result = NIL;
result = list_concat(result, cxt.sequences);
result = list_concat(result, cxt.tables);
result = list_concat(result, cxt.views);
result = list_concat(result, cxt.indexes);
result = list_concat(result, cxt.triggers);
result = list_concat(result, cxt.grants);
return result;
}
/*
* Traverse a fully-analyzed tree to verify that parameter symbols
* match their types. We need this because some Params might still
* be UNKNOWN, if there wasn't anything to force their coercion,
* and yet other instances seen later might have gotten coerced.
*/
static bool check_parameter_resolution_walker(
Node *node, check_parameter_resolution_context *context) {
if (node == NULL) return false;
if (IsA(node, Param)) {
Param *param = (Param *)node;
if (param->paramkind == PARAM_EXTERN) {
int paramno = param->paramid;
if (paramno <= 0 || /* shouldn't happen, but... */
paramno > context->numParams)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER),
errmsg("there is no parameter $%d", paramno)));
if (param->paramtype != context->paramTypes[paramno - 1])
ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
errmsg("could not determine data type of parameter $%d",
paramno)));
}
return false;
}
if (IsA(node, Query)) {
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
return query_tree_walker((Query *)node, check_parameter_resolution_walker,
(void *)context, 0);
}
return expression_tree_walker(node, check_parameter_resolution_walker,
(void *)context);
}
static void setQryDistributionPolicy(SelectStmt *stmt, Query *qry) {
ListCell *keys = NULL;
GpPolicy *policy = NULL;
int colindex = 0;
int maxattrs = 200;
if (Gp_role != GP_ROLE_DISPATCH) return;
/*
* Set default bucketnum for below case:
* CREATE TABLE ... WITH (bucketnum = ...) AS (SELECT * FROM ...)
*/
policy = (GpPolicy *)palloc(sizeof(GpPolicy) +
maxattrs * sizeof(policy->attrs[0]));
policy->ptype = POLICYTYPE_PARTITIONED;
policy->nattrs = 0;
policy->attrs[0] = 1;
if (stmt->intoClause != NULL)
policy->bucketnum = GetRelOpt_bucket_num_fromOptions(
stmt->intoClause->options, GetDefaultPartitionNum());
if (stmt->distributedBy) {
/*
* We have a DISTRIBUTED BY column list specified by the user
* Process it now and set the distribution policy.
*/
if (stmt->distributedBy->length != 1 ||
(list_head(stmt->distributedBy) != NULL &&
linitial(stmt->distributedBy) != NULL)) {
foreach (keys, stmt->distributedBy) {
char *key = strVal(lfirst(keys));
bool found = false;
AttrNumber n;
for (n = 1; n <= list_length(qry->targetList); n++) {
TargetEntry *target = get_tle_by_resno(qry->targetList, n);
colindex = n;
if (target->resname && strcmp(target->resname, key) == 0) {
found = true;
} /*if*/
if (found) break;
} /*for*/
if (!found)
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg(
"column \"%s\" named in DISTRIBUTED BY "
"clause does not exist",
key),
errOmitLocation(true)));
policy->attrs[policy->nattrs++] = colindex;
} /*foreach */
}
List *options = stmt->intoClause != NULL ? stmt->intoClause->options : NULL;
if (policy->nattrs > 0) {
policy->bucketnum =
GetRelOpt_bucket_num_fromOptions(options, GetHashDistPartitionNum());
} else {
policy->bucketnum =
GetRelOpt_bucket_num_fromOptions(options, GetDefaultPartitionNum());
}
}
qry->intoPolicy = policy;
}
/*
* getLikeDistributionPolicy
*
* For Greenplum Database distributed tables, default to
* the same distribution as the first LIKE table, unless
* we also have INHERITS
*/
static List *getLikeDistributionPolicy(InhRelation *e) {
List *likeDistributedBy = NIL;
Oid relId;
GpPolicy *oldTablePolicy;
relId = RangeVarGetRelid(e->relation, false, true /*allowHcatalog*/);
oldTablePolicy = GpPolicyFetch(CurrentMemoryContext, relId);
if (oldTablePolicy != NULL &&
oldTablePolicy->ptype == POLICYTYPE_PARTITIONED) {
int ia;
if (oldTablePolicy->nattrs > 0) {
for (ia = 0; ia < oldTablePolicy->nattrs; ia++) {
char *attname = get_attname(relId, oldTablePolicy->attrs[ia]);
if (likeDistributedBy)
likeDistributedBy =
lappend(likeDistributedBy, (Node *)makeString(attname));
else
likeDistributedBy = list_make1((Node *)makeString(attname));
}
} else { /* old table is distributed randomly. */
likeDistributedBy = list_make1((Node *)NULL);
}
}
return likeDistributedBy;
}
/*
* transformSingleRowErrorHandling
*
* If Single row error handling was specified with an error table, we first
* check if a table with the same name already exists. If yes, we verify that
* it has the correct pre-defined error table metadata format (and throw an
* error is it doesn't). If such a table doesn't exist, we add a CREATE TABLE
* statement to the before list of the CREATE EXTERNAL TABLE so that it will
* be created before the external table.
*/
static void transformSingleRowErrorHandling(ParseState *pstate,
CreateStmtContext *cxt,
SingleRowErrorDesc *sreh) {
/* check if error relation exists already */
Oid errreloid =
RangeVarGetRelid(sreh->errtable, true, false /*allowHcatalog*/);
bool errrelexists = OidIsValid(errreloid);
/*
* auto-generate a new error table or verify an existing one
*/
if (errrelexists) {
/* disable for hdfs protocol text error table */
if (sreh->is_hdfs_protocol_text)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg(
"Error table for hdfs protocol text/csv can't reuse "
"existing table"),
errOmitLocation(true)));
/*
* this table already exists in the database. Verify that specified
* table is a valid error table
*/
Relation rel;
rel = heap_openrv(sreh->errtable, AccessShareLock);
ValidateErrorTableMetaData(rel);
relation_close(rel, AccessShareLock);
sreh->reusing_existing_errtable = true;
} else {
/*
* no such table. auto create it by adding a CREATE TABLE <errortable>
* to the before list
*/
List *attrList;
int i = 0;
ereport(NOTICE, (errmsg(
"Error table \"%s\" does not exist. Auto generating an "
"error table with the same name",
sreh->errtable->relname)));
/*
* create a list of ColumnDef nodes based on the names and types of the
* per-defined error table columns
*/
attrList = NIL;
for (i = 1; i <= NUM_ERRORTABLE_ATTR; i++) {
ColumnDef *coldef = makeNode(ColumnDef);
coldef->inhcount = 0;
coldef->is_local = true;
coldef->is_not_null = false;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
coldef->constraints = NIL;
switch (i) {
case errtable_cmdtime:
coldef->typname = makeTypeNameFromOid(TIMESTAMPTZOID, -1);
coldef->colname = "cmdtime";
break;
case errtable_relname:
coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
coldef->colname = "relname";
break;
case errtable_filename:
coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
coldef->colname = "filename";
break;
case errtable_linenum:
coldef->typname = makeTypeNameFromOid(INT4OID, -1);
coldef->colname = "linenum";
break;
case errtable_bytenum:
coldef->typname = makeTypeNameFromOid(INT4OID, -1);
coldef->colname = "bytenum";
break;
case errtable_errmsg:
coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
coldef->colname = "errmsg";
break;
case errtable_rawdata:
coldef->typname = makeTypeNameFromOid(TEXTOID, -1);
coldef->colname = "rawdata";
break;
case errtable_rawbytes:
coldef->typname = makeTypeNameFromOid(BYTEAOID, -1);
coldef->colname = "rawbytes";
break;
}
attrList = lappend(attrList, coldef);
}
if (!sreh->is_hdfs_protocol_text) {
CreateStmt *createStmt = makeNode(CreateStmt);
createStmt->base.relation = sreh->errtable;
createStmt->base.tableElts = attrList;
createStmt->base.inhRelations = NIL;
createStmt->base.constraints = NIL;
createStmt->base.options =
list_make2(makeDefElem("errortable", (Node *)makeString("true")),
makeDefElem("appendonly", (Node *)makeString("true")));
createStmt->base.oncommit = ONCOMMIT_NOOP;
createStmt->base.tablespacename = NULL;
createStmt->base.relKind = RELKIND_RELATION;
createStmt->relStorage = RELSTORAGE_AOROWS;
createStmt->base.distributedBy =
list_make1(NULL); /* DISTRIBUTED RANDOMLY */
cxt->blist = lappend(cxt->blist, createStmt);
} else {
CreateExternalStmt *createExternalStmt = makeNode(CreateExternalStmt);
createExternalStmt->base.relation = sreh->errtable;
createExternalStmt->isexternal = true;
createExternalStmt->base.tableElts = attrList;
createExternalStmt->base.distributedBy = NULL;
createExternalStmt->sreh = NULL;
createExternalStmt->policy = NULL;
createExternalStmt->isweb = false;
createExternalStmt->iswritable = false;
createExternalStmt->format = pstrdup("csv");
createExternalStmt->forceCreateDir = true;
ExtTableTypeDesc *desc = makeNode(ExtTableTypeDesc);
desc->exttabletype = EXTTBL_TYPE_LOCATION;
desc->on_clause = NIL;
desc->command_string = NULL;
desc->location_list = list_make1(makeString(pstrdup(sreh->hdfsLoc)));
createExternalStmt->exttypedesc = desc;
createExternalStmt->base.options = NULL;
createExternalStmt->encoding = list_make1(makeDefElem(
"encoding",
(Node *)makeString(pg_encoding_to_char(pg_get_client_encoding()))));
cxt->blist = lappend(cxt->blist, createExternalStmt);
}
sreh->reusing_existing_errtable = false;
}
}
/*
* create a policy with random distribution
*/
GpPolicy *createRandomDistribution(int maxattrs) {
GpPolicy *p = NULL;
p = (GpPolicy *)palloc(sizeof(GpPolicy) + maxattrs * sizeof(p->attrs[0]));
p->ptype = POLICYTYPE_PARTITIONED;
p->nattrs = 0;
p->attrs[0] = 1;
return p;
}
/*
* get default filespace path for hdfs, data of hawq is stored in hdfs and magma,
* there is only hdfs protocol in need of filespace path.
*/
char *getDefaultFilespace() {
//need to free
char *ret = (char *)palloc0(MAXPGPATH);
sprintf(ret, "%s%s", PROTOCOL_HDFS, dfs_url);
return ret;
}