| /* |
| * 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 "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; |
| CreateStmt *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 *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, CreateStmt *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, CreateStmt *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, CreateStmt *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, CreateStmt *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); |
| /* |
| * 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 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) |
| { |
| /* |
| * 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_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; |
| |
| /* |
| * 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; |
| |
| /* set up range table with just the result rel */ |
| qry->resultRelation = setTargetTable(pstate, stmt->relation, |
| interpretInhOption(stmt->relation->inhOpt), |
| true, |
| ACL_DELETE); |
| |
| 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; |
| |
| 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; |
| } |
| |
| /* |
| * 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); |
| |
| /* 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; |
| } |
| |
| /* |
| * 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.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); |
| } |
| |
| /* |
| * 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, 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 |
| }; |
| |
| typedef 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 |
| }PreDefinedFormatterOptionID; |
| |
| 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_UNPREDEFINED; /* 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_UNPREDEFINED; /* 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_UNPREDEFINED; /* 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 *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))); |
| } |
| |
| /* orc, text, csv on hdfs */ |
| else if (pg_strncasecmp(stmt->format, "orc", strlen("orc")) == 0 || |
| 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) { |
| |
| 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"))) { |
| ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg( |
| "Internal table on hdfs must be \'orc\', " |
| "\'text\', or \'csv\' format"))); |
| } |
| isPluggableStorage = true; |
| } else { |
| Value *loc_val = lfirst(loc_cell); |
| char *loc_str = pstrdup(loc_val->val.str); |
| bool is_hdfs_protocol = IS_HDFS_URI(loc_str); |
| isPluggableStorage = is_hdfs_protocol; |
| |
| |
| 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); |
| /* uuid_t uuid; |
| char buf[1024]; |
| uuid_generate(uuid); |
| uuid_unparse(uuid, buf); |
| sprintf(errDesc->hdfsLoc, "%s/ExtErrTbl/%s", fileSpacePath, buf);*/ |
| } |
| } |
| |
| // 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; |
| |
| 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->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 (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; |
| } |
| |
| 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; |
| |
| foreach(entry, cxt->inhRelations) |
| { |
| RangeVar *parent = (RangeVar *) lfirst(entry); |
| Oid relId = RangeVarGetRelid(parent, false, false /*allowHcatalog*/); |
| 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 (policy->bucketnum == -1) |
| 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; |
| |
| 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("When there is both a PRIMARY KEY, and a " |
| "DISTRIBUTED BY clause, the DISTRIBUTED BY " |
| "clause must be equal to or a left-subset " |
| "of the PRIMARY KEY"), |
| 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 parquetTable = 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) |
| parquetTable = 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(!parquetTable) |
| 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) && (parquetTable == 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, CreateStmt *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->base.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, CreateStmt *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->base.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, CreateStmt *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, |
| CreateStmt *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, |
| CreateStmt *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(CreateStmt *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->base.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->base.tableElts = list_concat(others, finalencs); |
| } |
| |
| static void |
| make_child_node(CreateStmt *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); |
| CreateStmt *child_tab_stmt = makeNode(CreateStmt); |
| |
| 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->base.relation = child_tab_name; |
| child_tab_stmt->base.is_part_child = true; |
| child_tab_stmt->base.is_add_part = stmt->base.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->base.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->base.tableElts = copyObject(stmt->base.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->base.tableElts = |
| lappend(child_tab_stmt->base.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->base.inhRelations = list_copy(stmt->base.inhRelations); |
| |
| child_tab_stmt->base.constraints = copyObject(stmt->base.constraints); |
| child_tab_stmt->base.options = stmt->base.options; |
| |
| /* allow WITH clause for appendonly tables */ |
| if ( pStoreAttr ) |
| { |
| AlterPartitionCmd *psa_apc = (AlterPartitionCmd *)pStoreAttr; |
| |
| /* Options */ |
| if ( psa_apc->arg1 ) |
| child_tab_stmt->base.options = (List *)psa_apc->arg1; |
| /* Tablespace from parent (input CreateStmt)... */ |
| if ( psa_apc->arg2 && *strVal(psa_apc->arg2) ) |
| child_tab_stmt->base.tablespacename = strVal(psa_apc->arg2); |
| } |
| /* ...or tablespace from root. */ |
| if ( !child_tab_stmt->base.tablespacename && stmt->base.tablespacename ) |
| child_tab_stmt->base.tablespacename = stmt->base.tablespacename; |
| |
| child_tab_stmt->base.oncommit = stmt->base.oncommit; |
| child_tab_stmt->base.distributedBy = stmt->base.distributedBy; |
| |
| /* use the newSub as the partitionBy if the current |
| * partition elem had an inline subpartition declaration |
| */ |
| child_tab_stmt->base.partitionBy = (Node *)newSub; |
| |
| child_tab_stmt->base.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->base.tablespacename ) |
| { |
| Oid poid = RangeVarGetRelid(cxt->relation, true, false /*allowHcatalog*/); /* parent branch */ |
| |
| if ( ! poid ) |
| { |
| poid = RangeVarGetRelid(stmt->base.relation, true, false /*alloweHcatalog*/); /* whole partitioned table */ |
| } |
| if ( poid ) |
| { |
| Relation prel = RelationIdGetRelation(poid); |
| child_tab_stmt->base.tablespacename = get_tablespace_name(prel->rd_rel->reltablespace); |
| RelationClose(prel); |
| } |
| } |
| |
| cxt->alist = lappend(cxt->alist, child_tab_stmt); |
| |
| /* |
| * 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); |
| |
| /* 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); |
| ats->relkind = OBJECT_TABLE; |
| |
| /* 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, |
| CreateStmt *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->base.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->base.partitionBy = NULL; |
| stmt->base.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; |
| |
| /* |
| * 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; |
| |
| 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; |
| |
| /* 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; |
| |
| qry->resultRelation = setTargetTable(pstate, stmt->relation, |
| interpretInhOption(stmt->relation->inhOpt), |
| true, |
| ACL_UPDATE); |
| |
| /* |
| * 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); |
| |
| 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))); |
| |
| 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; |
| |
| 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; |
| CreateStmt *ct; |
| InhRelation *inh = makeNode(InhRelation); |
| List *cl = NIL; |
| |
| Assert(IsA(pc2->arg2, List)); |
| ct = (CreateStmt *)linitial((List *)pc2->arg2); |
| |
| 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 |
| */ |
| 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) |
| { |
| /* |
| * 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 |
| */ |
| |
| CreateStmt *createStmt; |
| 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))); |
| createStmt = makeNode(CreateStmt); |
| |
| /* |
| * 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); |
| } |
| |
| 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); |
| |
| 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; |
| } |