| /* |
| * 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. |
| */ |
| |
| /*-------------------------------------------------------------------------- |
| * |
| * cdbpartition.c |
| * Provides utility routines to support sharding via partitioning |
| * within Greenplum Database. |
| * |
| * Many items are just extensions of tablecmds.c. |
| * |
| *-------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| #include "funcapi.h" |
| #include "access/genam.h" |
| #include "access/hash.h" |
| #include "access/heapam.h" |
| #include "access/reloptions.h" |
| #include "catalog/catquery.h" |
| #include "catalog/dependency.h" |
| #include "catalog/heap.h" |
| #include "catalog/pg_constraint.h" |
| #include "catalog/pg_inherits.h" |
| #include "catalog/pg_type.h" |
| #include "catalog/pg_operator.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_partition_encoding.h" |
| #include "catalog/namespace.h" |
| #include "cdb/cdbpartition.h" |
| #include "cdb/cdbvars.h" |
| #include "commands/defrem.h" |
| #include "commands/tablecmds.h" |
| #include "commands/tablespace.h" |
| #include "nodes/makefuncs.h" |
| #include "optimizer/var.h" |
| #include "parser/analyze.h" |
| #include "parser/parse_expr.h" |
| #include "parser/parse_oper.h" |
| #include "parser/parse_relation.h" |
| #include "parser/parse_target.h" |
| #include "tcop/utility.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/datum.h" |
| #include "utils/elog.h" |
| #include "utils/fmgroids.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| #include "utils/syscache.h" |
| |
| #define DEFAULT_CONSTRAINT_ESTIMATE 16 |
| #define MIN_XCHG_CONTEXT_SIZE 4096 |
| #define INIT_XCHG_BLOCK_SIZE 4096 |
| #define MAX_XCHG_BLOCK_SIZE 4096 |
| |
| typedef struct |
| { |
| char *key; |
| List *table_cons; |
| List *part_cons; |
| List *cand_cons; |
| } ConstraintEntry; |
| |
| typedef struct |
| { |
| Node *entry; |
| } ConNodeEntry; |
| |
| static void |
| record_constraints(Relation pgcon, MemoryContext context, |
| HTAB *hash_tbl, Relation rel, |
| PartExchangeRole xrole); |
| |
| static char * |
| constraint_names(List *cons); |
| |
| static void |
| constraint_diffs(List *cons_a, List *cons_b, bool match_names, List **missing, List **extra); |
| |
| static void add_template_encoding_clauses(Oid relid, Oid paroid, List *stenc); |
| |
| static PartitionNode * |
| findPartitionNodeEntry(PartitionNode *partitionNode, Oid partOid); |
| |
| static uint32 |
| constrNodeHash(const void *keyPtr, Size keysize); |
| |
| static int |
| constrNodeMatch(const void *keyPtr1, const void *keyPtr2, Size keysize); |
| /* |
| * Hash keys are null-terminated C strings assumed to be stably |
| * allocated. We accomplish this by allocating them in a context |
| * that lives as long as the hash table that contains them. |
| */ |
| |
| /* Hash entire string. */ |
| static uint32 key_string_hash(const void *key, Size keysize) |
| { |
| Size s_len = strlen((const char *) key); |
| |
| Assert(keysize == sizeof(char*)); |
| return DatumGetUInt32(hash_any((const unsigned char *) key, (int) s_len)); |
| } |
| |
| /* Compare entire string. */ |
| static int key_string_compare(const void *key1, const void *key2, Size keysize) |
| { |
| Assert(keysize == sizeof(char*)); |
| return strcmp(((ConstraintEntry*)key1)->key, key2); |
| } |
| |
| /* Copy string by copying pointer. */ |
| static void *key_string_copy(void *dest, const void *src, Size keysize) |
| { |
| Assert(keysize == sizeof(char*)); |
| |
| *((char**)dest) = (char*)src; /* trust caller re allocation */ |
| return NULL; /* not used */ |
| } |
| |
| static char parttype_to_char(PartitionByType type); |
| static void add_partition(Partition *part); |
| static void add_partition_rule(PartitionRule *rule); |
| static Oid get_part_oid(Oid rootrelid, int16 parlevel, bool istemplate); |
| static Datum *magic_expr_to_datum(Relation rel, PartitionNode *partnode, |
| Node *expr, bool **ppisnull); |
| static Oid selectPartitionByRank(PartitionNode *partnode, int rnk); |
| static bool compare_partn_opfuncid(PartitionNode *partnode, |
| char *pub, char *compare_op, |
| List *colvals, |
| Datum *values, bool *isnull, |
| TupleDesc tupdesc); |
| static PartitionNode * |
| selectListPartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| Oid *foundOid, PartitionRule **prule, Oid exprTypid); |
| static Oid get_less_than_oper(Oid lhstypid, Oid rhstypid, bool strictlyless); |
| static FmgrInfo *get_less_than_comparator(int keyno, PartitionRangeState *rs, Oid ruleTypeOid, Oid exprTypeOid, bool strictlyless, bool is_direct); |
| static int range_test(Datum tupval, Oid ruleTypeOid, Oid exprTypeOid, PartitionRangeState *rs, int keyno, |
| PartitionRule *rule); |
| static PartitionNode * |
| selectRangePartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| Oid *foundOid, int *pSearch, PartitionRule **prule, Oid exprTypid); |
| static PartitionNode * |
| selectHashPartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| Oid *found, PartitionRule **prule); |
| static Oid |
| selectPartition1(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| int *pSearch, |
| PartitionNode **ppn_out); |
| static int |
| atpxPart_validate_spec( |
| PartitionBy *pBy, |
| CreateStmtContext *pcxt, |
| Relation rel, |
| CreateStmt *ct, |
| PartitionElem *pelem, |
| PartitionNode *pNode, |
| Node *partName, |
| bool isDefault, |
| PartitionByType part_type, |
| char *partDesc); |
| |
| static void atpxSkipper(PartitionNode *pNode, int *skipped); |
| |
| static List * |
| build_rename_part_recurse(PartitionRule *rule, const char *old_parentname, |
| const char *new_parentname, |
| int *skipped); |
| static Oid |
| get_opfuncid_by_opname(List *opname, Oid lhsid, Oid rhsid); |
| |
| static PgPartRule * |
| get_pprule_from_ATC(Relation rel, AlterTableCmd *cmd); |
| |
| static List* |
| get_partition_rules(PartitionNode *pn); |
| |
| static bool |
| relation_has_supers(Oid relid); |
| |
| static NewConstraint * |
| constraint_apply_mapped(HeapTuple tuple, AttrMap *map, Relation cand, |
| bool validate, bool is_split, Relation pgcon); |
| |
| static char * |
| ChooseConstraintNameForPartitionCreate(const char *rname, |
| const char *cname, |
| const char *label, |
| List *used_names); |
| |
| static Bitmapset * |
| get_partition_key_bitmapset(Oid relid); |
| |
| static List *get_deparsed_partition_encodings(Oid relid, Oid paroid); |
| static List *rel_get_leaf_relids_from_rule(Oid ruleOid); |
| |
| /* Is the given relation the top relation of a partitioned table? |
| * |
| * exists (select * |
| * from pg_partition |
| * where parrelid = relid) |
| * |
| * False for interior branches and leaves or when called other |
| * then on the entry database, i.e., only meaningful on the |
| * entry database. |
| */ |
| bool |
| rel_is_partitioned(Oid relid) |
| { |
| return ( |
| (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_partition " |
| " WHERE parrelid = :1 ", |
| ObjectIdGetDatum(relid))) > 0)); |
| } |
| |
| /* |
| * Return an integer list of the attribute numbers of the partitioning |
| * key of the partitioned table identified by the argument or NIL. |
| * |
| * This is similar to get_partition_attrs but is driven by OID and |
| * the partition catalog, not by a PartitionNode. |
| * |
| * Note: Only returns a non-empty list of keys for partitioned table |
| * as a whole. Returns empty for non-partitioned tables or for |
| * parts of partitioned tables. Key attributes are attribute |
| * numbers in the partitioned table. |
| */ |
| List * |
| rel_partition_key_attrs(Oid relid) |
| { |
| Relation rel; |
| ScanKeyData key; |
| SysScanDesc scan; |
| HeapTuple tuple; |
| List *pkeys = NIL; |
| |
| /* Table pg_partition is only populated on the entry database, |
| * however, we disable calls from outside dispatch to foil use |
| * of utility mode. (Full UCS may may this test obsolete.) |
| */ |
| if (Gp_session_role != GP_ROLE_DISPATCH ) |
| elog(ERROR, "mode not dispatch"); |
| |
| rel = heap_open(PartitionRelationId, AccessShareLock); |
| |
| ScanKeyInit(&key, |
| Anum_pg_partition_parrelid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(relid)); |
| |
| |
| scan = systable_beginscan(rel, PartitionParrelidIndexId, true, |
| SnapshotNow, 1, &key); |
| |
| tuple = systable_getnext(scan); |
| |
| while ( HeapTupleIsValid(tuple) ) |
| { |
| Index i; |
| Form_pg_partition p = (Form_pg_partition) GETSTRUCT(tuple); |
| |
| if (p->paristemplate) |
| { |
| tuple = systable_getnext(scan); |
| continue; |
| } |
| |
| for ( i = 0; i < p->parnatts; i++ ) |
| { |
| pkeys = lappend_int(pkeys, (Oid)p->paratts.values[i]); |
| } |
| |
| tuple = systable_getnext(scan); |
| } |
| |
| systable_endscan(scan); |
| heap_close(rel, AccessShareLock); |
| |
| return pkeys; |
| } |
| |
| /* |
| * Return a list of lists representing the partitioning keys of the partitioned |
| * table identified by the argument or NIL. The keys are in the order of |
| * partitioning levels. Each of the lists inside the main list correspond to one |
| * level, and may have one or more attribute numbers depending on whether the |
| * part key for that level is composite or not. |
| * |
| * Note: Only returns a non-empty list of keys for partitioned table |
| * as a whole. Returns empty for non-partitioned tables or for |
| * parts of partitioned tables. Key attributes are attribute |
| * numbers in the partitioned table. |
| */ |
| List * |
| rel_partition_keys_ordered(Oid relid) |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| List *levels = NIL; |
| List *keysUnordered = NIL; |
| int nlevels = 0; |
| HeapTuple tuple = NULL; |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| Form_pg_partition p = (Form_pg_partition) GETSTRUCT(tuple); |
| |
| if (p->paristemplate) |
| { |
| continue; |
| } |
| |
| List *levelkeys = NIL; |
| for (int i = 0; i < p->parnatts; i++ ) |
| { |
| levelkeys = lappend_int(levelkeys, (Oid)p->paratts.values[i]); |
| } |
| |
| nlevels++; |
| levels = lappend_int(levels, p->parlevel); |
| keysUnordered = lappend(keysUnordered, levelkeys); |
| } |
| caql_endscan(pcqCtx); |
| |
| if (1 == nlevels) |
| { |
| list_free(levels); |
| return keysUnordered; |
| } |
| |
| // now order the keys by level |
| List *pkeys = NIL; |
| for (int i = 0; i< nlevels; i++) |
| { |
| int pos = list_find_int(levels, i); |
| Assert (0 <= pos); |
| |
| pkeys = lappend(pkeys, list_nth(keysUnordered, pos)); |
| } |
| list_free(levels); |
| list_free(keysUnordered); |
| |
| return pkeys; |
| } |
| |
| /* |
| * Is relid a child in a partitioning hierarchy? |
| * |
| * exists (select * |
| * from pg_partition_rule |
| * where parchildrelid = relid) |
| * |
| * False for the partitioned table as a whole or when called |
| * other then on the entry database, i.e., only meaningful on |
| * the entry database. |
| */ |
| bool |
| rel_is_child_partition(Oid relid) |
| { |
| return ( |
| (caql_getcount( |
| NULL, |
| cql("SELECT COUNT(*) FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(relid))) > 0)); |
| } |
| |
| /* |
| * Is relid a leaf node of a partitioning hierarchy? If no, it does not follow |
| * that it is the root. |
| * |
| * To determine if it is a leaf or not, we need to find the depth of our |
| * partition and compare this to the maximum depth of the partition set itself. |
| * If they're equal, we have a leaf, otherwise, something else. |
| * |
| * Call only on entry database. |
| */ |
| bool |
| rel_is_leaf_partition(Oid relid) |
| { |
| HeapTuple tuple; |
| Oid paroid = InvalidOid; |
| int maxdepth = 0; |
| int mylevel = 0; |
| int fetchCount = 0; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| Oid partitioned_rel = InvalidOid; /* OID of the root table of the |
| * partition set |
| */ |
| |
| /* |
| * Find the pg_partition_rule entry to see if this is a child at |
| * all and, if so, to locate the OID for the pg_partition entry. |
| */ |
| paroid = |
| caql_getoid_plus(NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT paroid FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!OidIsValid(paroid) || !fetchCount) |
| return false; |
| |
| tuple = caql_getfirst(NULL, |
| cql("SELECT * FROM pg_partition " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(paroid))); |
| |
| Insist(HeapTupleIsValid(tuple)); |
| |
| mylevel = ((Form_pg_partition)GETSTRUCT(tuple))->parlevel; |
| partitioned_rel = ((Form_pg_partition)GETSTRUCT(tuple))->parrelid; |
| |
| pcqCtx = caql_beginscan( |
| cqclr(&cqc), |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 ", |
| ObjectIdGetDatum(partitioned_rel))); |
| |
| /* |
| * Of course, we could just maxdepth++ but this seems safer -- we |
| * don't have to worry about the starting depth being 0, 1 or |
| * something else. |
| */ |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| /* not interested in templates */ |
| if (((Form_pg_partition)GETSTRUCT(tuple))->paristemplate == false) |
| { |
| int depth = ((Form_pg_partition)GETSTRUCT(tuple))->parlevel; |
| maxdepth = Max(maxdepth, depth); |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| return maxdepth == mylevel; |
| } |
| |
| /* Determine the status of the given table with respect to partitioning. |
| * |
| * Uses lower level routines. Returns PART_STATUS_NONE for a non-partitioned |
| * table or when called other then on the entry database, i.e., only meaningful |
| * on the entry database. |
| */ |
| PartStatus rel_part_status(Oid relid) |
| { |
| if (Gp_role != GP_ROLE_DISPATCH) |
| { |
| ereport(DEBUG1, |
| (errmsg("requesting part status outside dispatch - returning none"))); |
| return PART_STATUS_NONE; |
| } |
| |
| if ( rel_is_partitioned(relid) ) |
| { |
| Assert( !rel_is_child_partition(relid) && !rel_is_leaf_partition(relid) ); |
| return PART_STATUS_ROOT; |
| } |
| else /* not an actual partitioned table root */ |
| { |
| if ( rel_is_child_partition(relid) ) |
| return rel_is_leaf_partition(relid) ? PART_STATUS_LEAF : PART_STATUS_INTERIOR; |
| else /* not a part of a partitioned table */ |
| Assert( !rel_is_child_partition(relid) ); |
| } |
| return PART_STATUS_NONE; |
| } |
| |
| |
| /* Locate all the constraints on the given open relation (rel) and |
| * record them in the hash table (hash_tbl) of ConstraintEntry |
| * structs. |
| * |
| * Depending on the value of xrole, the given relation must be either |
| * the root, an existing part, or an exchange candidate for the same |
| * partitioned table. A copy of each constraint tuple found is |
| * appended to the corresponding field of the hash entry. |
| * |
| * The key of the hash table is a string representing the constraint |
| * in SQL. This should be comparable across parts of a partitioning |
| * hierarchy regardless of the history (hole pattern) or storage type |
| * of the table. |
| * |
| * Note that pgcon (the ConstraintRelationId appropriately locked) |
| * is supplied externally for efficiency. No other relation should |
| * be supplied via this argument. |
| * |
| * Memory allocated in here (strings, tuples, lists, list cells, etc) |
| * is all associated with the hash table and is allocated in the given |
| * memory context, so it will be easy to free in bulk. |
| */ |
| void |
| record_constraints(Relation pgcon, |
| MemoryContext context, |
| HTAB *hash_tbl, |
| Relation rel, |
| PartExchangeRole xrole) |
| { |
| HeapTuple tuple; |
| Oid conid; |
| char *condef; |
| ConstraintEntry *entry; |
| bool found; |
| MemoryContext oldcontext; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), pgcon), |
| cql("SELECT * FROM pg_constraint " |
| " WHERE conrelid = :1 ", |
| ObjectIdGetDatum(RelationGetRelid(rel)))); |
| |
| /* For each constraint on rel: */ |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| oldcontext = MemoryContextSwitchTo(context); |
| |
| conid = HeapTupleGetOid(tuple); |
| |
| condef = pg_get_constraintexpr_string(conid); |
| entry = (ConstraintEntry*)hash_search(hash_tbl, |
| (void*) condef, |
| HASH_ENTER, |
| &found); |
| |
| /* A tuple isn't a Node, but we'll stick it in a List |
| * anyway, and just be careful. |
| */ |
| if ( !found ) |
| { |
| entry->key = condef; |
| entry->table_cons = NIL; |
| entry->part_cons = NIL; |
| entry->cand_cons = NIL; |
| } |
| tuple = heap_copytuple(tuple); |
| |
| switch(xrole) |
| { |
| case PART_TABLE: |
| entry->table_cons = lappend(entry->table_cons, tuple); |
| break; |
| case PART_PART: |
| entry->part_cons = lappend(entry->part_cons, tuple); |
| break; |
| case PART_CAND: |
| entry->cand_cons = lappend(entry->cand_cons, tuple); |
| break; |
| default: |
| Assert(FALSE); |
| } |
| |
| MemoryContextSwitchTo(oldcontext); |
| } |
| caql_endscan(pcqCtx); |
| } |
| |
| /* Subroutine of ATPExecPartExchange used to swap constraints on existing |
| * part and candidate part. Note that this runs on both the QD and QEs |
| * so must not assume availability of partition catalogs. |
| * |
| * table -- open relation of the parent partitioned table |
| * part -- open relation of existing part to exchange |
| * cand -- open relation of the candidate part |
| * validate -- whether to collect constraints into a result list for |
| * enforcement during phase 3 (WITH/WITHOUT VALIDATION). |
| */ |
| List * |
| cdb_exchange_part_constraints(Relation table, |
| Relation part, |
| Relation cand, |
| bool validate, |
| bool is_split) |
| { |
| HTAB *hash_tbl; |
| HASHCTL hash_ctl; |
| HASH_SEQ_STATUS hash_seq; |
| Relation pgcon; |
| MemoryContext context; |
| MemoryContext oldcontext; |
| ConstraintEntry *entry; |
| AttrMap *p2t = NULL; |
| AttrMap *c2t = NULL; |
| |
| HeapTuple tuple; |
| Form_pg_constraint con; |
| |
| List *excess_constraints = NIL; |
| List *missing_constraints = NIL; |
| List *missing_part_constraints = NIL; |
| List *validation_list = NIL; |
| int delta_checks = 0; |
| |
| |
| /* Setup an empty hash table mapping constraint definition |
| * strings to ConstraintEntry structures. |
| */ |
| context = AllocSetContextCreate(CurrentMemoryContext, |
| "Constraint Exchange Context", |
| MIN_XCHG_CONTEXT_SIZE, |
| INIT_XCHG_BLOCK_SIZE, |
| MAX_XCHG_BLOCK_SIZE); |
| |
| memset(&hash_ctl, 0, sizeof(hash_ctl)); |
| hash_ctl.keysize = sizeof(char*); |
| hash_ctl.entrysize = sizeof(ConstraintEntry); |
| hash_ctl.hash = key_string_hash; |
| hash_ctl.match = key_string_compare; |
| hash_ctl.keycopy = key_string_copy; |
| hash_ctl.hcxt = context; |
| hash_tbl = hash_create("Constraint Exchange Map", |
| DEFAULT_CONSTRAINT_ESTIMATE, |
| &hash_ctl, |
| HASH_ELEM | HASH_FUNCTION | |
| HASH_COMPARE | HASH_KEYCOPY | |
| HASH_CONTEXT); |
| |
| /* Open pg_constraint here for use in the subroutine and below. */ |
| pgcon = heap_open(ConstraintRelationId, AccessShareLock); |
| |
| /* We need attribute numbers normalized to the partitioned table. |
| * Note that these maps are inverse to the usual table-to-part maps. |
| */ |
| oldcontext = MemoryContextSwitchTo(context); |
| map_part_attrs(part, table, &p2t, TRUE); |
| map_part_attrs(cand, table, &c2t, TRUE); |
| MemoryContextSwitchTo(oldcontext); |
| |
| /* Find and record constraints on the players. */ |
| record_constraints(pgcon, context, hash_tbl, table, PART_TABLE); |
| record_constraints(pgcon, context, hash_tbl, part, PART_PART); |
| record_constraints(pgcon, context, hash_tbl, cand, PART_CAND); |
| hash_freeze(hash_tbl); |
| |
| /* Each entry in the hash table represents a single logically equivalent |
| * constraint which may appear zero or more times (under different names) |
| * on each of the three involved relations. By construction, it will |
| * appear on at least one list. |
| * |
| * For each hash table entry: |
| */ |
| hash_seq_init(&hash_seq, hash_tbl); |
| while ((entry = hash_seq_search(&hash_seq))) |
| { |
| if ( list_length(entry->table_cons) > 0 ) |
| { |
| /* REGULAR CONSTRAINT |
| * |
| * Constraints on the whole partitioned table are regular (in |
| * the sense that they do not enforce partitioning rules and |
| * corresponding constraints must occur on every part). |
| */ |
| |
| List *missing = NIL; |
| List *extra = NIL; |
| |
| if ( list_length(entry->part_cons) == 0 ) |
| { |
| /* The regular constraint is missing from the existing part, |
| * so there is a database anomaly. Warn rather than issuing |
| * an error, because this may be an attempt to use EXCHANGE |
| * to correct the problem. There may be multiple constraints |
| * with different names, but report only the first name since |
| * the constraint expression itself is all that matters. |
| */ |
| tuple = linitial(entry->table_cons); |
| con = (Form_pg_constraint) GETSTRUCT(tuple); |
| |
| ereport(WARNING, |
| (errcode(ERRCODE_WARNING), |
| errmsg("ignoring inconsistency: \"%s\" " |
| "has no constraint corresponding to \"%s\" " |
| "on \"%s\"", |
| RelationGetRelationName(part), |
| NameStr(con->conname), |
| RelationGetRelationName(table)), |
| errOmitLocation(true))); |
| } |
| |
| /* The regular constraint should ultimately appear on the candidate |
| * part the same number of times and with the same name as it appears |
| * on the partitioned table. The call to constraint_diff will find |
| * matching names and we'll be left with occurances of the constraint |
| * that must be added to the candidate (missing) and occurances that |
| * must be dropped from the candidate (extra). |
| */ |
| constraint_diffs(entry->table_cons, entry->cand_cons, true, &missing, &extra); |
| missing_constraints = list_concat(missing_constraints, missing); |
| excess_constraints = list_concat(excess_constraints, extra); |
| } |
| else if ( list_length(entry->part_cons) > 0 ) /* and none on whole */ |
| { |
| /* PARTITION CONSTRAINT |
| * |
| * Constraints on the part and not the whole must guard a partition |
| * rule, so they must be CHECK constraints on partitioning columns. |
| * They are managed internally, so there must be only one of them. |
| * (Though a part will have a partition constraint for each partition |
| * level, a given constraint should appear only once per part.) |
| * |
| * They should either already occur on the candidate or be added. |
| * Partition constraint names are not carefully managed so they |
| * shouldn't be regarded as meaningful. |
| * |
| * Since we use the partition constraint of the part to check or |
| * construct the partition constraint of the candidate, we insist it |
| * is in good working order, and issue an error, if not. |
| */ |
| int n = list_length(entry->part_cons); |
| |
| if ( n > 1 ) |
| { |
| elog(ERROR, |
| "multiple partition constraints (same key) on \"%s\"", |
| RelationGetRelationName(part)); |
| } |
| |
| /* Get the model partition constraint. |
| */ |
| tuple = linitial(entry->part_cons); |
| con = (Form_pg_constraint) GETSTRUCT(tuple); |
| |
| /* Check it, though this is cursory in that we don't check that |
| * the right attributes are involved and that the semantics are |
| * right. |
| */ |
| if (con->contype != CONSTRAINT_CHECK) |
| { |
| elog(ERROR, |
| "invalid partition constraint on \"%s\"", |
| RelationGetRelationName(part)); |
| } |
| |
| n = list_length(entry->cand_cons); |
| |
| if ( n == 0 ) |
| { |
| /* The partition constraint is missing from the candidate and |
| * must be added. |
| */ |
| missing_part_constraints = lappend(missing_part_constraints, |
| (HeapTuple)linitial(entry->part_cons)); |
| } |
| else if ( n == 1 ) |
| { |
| /* One instance of the partition constraint exists on the |
| * candidate, so let's not worry about name drift. All is |
| * well. */ |
| } |
| else |
| { |
| /* Several instances of the partition constraint exist on |
| * the candidate. If one has a matching name, prefer it. |
| * Else, just chose the first (arbitrary). |
| */ |
| List *missing = NIL; |
| List *extra = NIL; |
| |
| constraint_diffs(entry->part_cons, entry->cand_cons, false, &missing, &extra); |
| |
| if ( list_length(missing) == 0 ) |
| { |
| excess_constraints = list_concat(excess_constraints, extra); |
| } |
| else /* missing */ |
| { |
| ListCell *lc; |
| bool skip = TRUE; |
| |
| foreach(lc, entry->cand_cons) |
| { |
| HeapTuple tuple = (HeapTuple)lfirst(lc); |
| if ( skip ) |
| { |
| skip = FALSE; |
| } |
| else |
| { |
| excess_constraints = lappend(excess_constraints, tuple); |
| } |
| } |
| } |
| } |
| } |
| else if ( list_length(entry->cand_cons) > 0 ) /* and none on whole or part */ |
| { |
| /* MAVERICK CONSTRAINT |
| * |
| * Constraints on only the candidate are extra and must be |
| * dropped before the candidate can replace the part. |
| */ |
| excess_constraints = list_concat(excess_constraints, |
| entry->cand_cons); |
| } |
| else /* Defensive: Can't happen that no constraints are set. */ |
| { |
| elog(ERROR, "constraint hash table inconsistent"); |
| } |
| } |
| |
| |
| if ( excess_constraints ) |
| { |
| /* Disallow excess constraints. We could drop them automatically, but they |
| * may carry semantic information about the candidate that is important to |
| * the user, so make the user decide whether to drop them. |
| */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION), |
| errmsg("invalid constraint(s) found on \"%s\": %s", |
| RelationGetRelationName(cand), |
| constraint_names(excess_constraints)), |
| errhint("drop the invalid constraints and retry"), |
| errOmitLocation(true))); |
| } |
| |
| |
| if ( missing_part_constraints ) |
| { |
| ListCell *lc; |
| |
| foreach(lc, missing_part_constraints) |
| { |
| HeapTuple missing_part_constraint = (HeapTuple)lfirst(lc); |
| /* We need a constraint like the missing one for the part, but translated |
| * for the candidate. |
| */ |
| AttrMap *map; |
| struct NewConstraint *nc; |
| Form_pg_constraint mcon = (Form_pg_constraint)GETSTRUCT(missing_part_constraint); |
| |
| if ( mcon->contype != CONSTRAINT_CHECK ) |
| elog(ERROR,"Invalid partition constration, not CHECK type"); |
| |
| map_part_attrs(part, cand, &map, TRUE); |
| nc = constraint_apply_mapped(missing_part_constraint, map, cand, |
| validate, is_split, pgcon); |
| if ( nc ) |
| validation_list = lappend(validation_list, nc); |
| |
| delta_checks++; |
| } |
| } |
| |
| if ( missing_constraints ) |
| { |
| /* We need constraints like the missing ones for the whole, but |
| * translated for the candidate. |
| */ |
| AttrMap *map; |
| struct NewConstraint *nc; |
| ListCell *lc; |
| |
| map_part_attrs(table, cand, &map, TRUE); |
| foreach(lc, missing_constraints) |
| { |
| HeapTuple tuple = (HeapTuple)lfirst(lc); |
| Form_pg_constraint mcon = (Form_pg_constraint)GETSTRUCT(tuple); |
| nc = constraint_apply_mapped(tuple, map, cand, |
| validate, is_split, pgcon); |
| if ( nc ) |
| validation_list = lappend(validation_list, nc); |
| |
| if ( mcon->contype == CONSTRAINT_CHECK ) |
| delta_checks++; |
| } |
| } |
| |
| if ( delta_checks ) |
| { |
| SetRelationNumChecks(cand, cand->rd_rel->relchecks + delta_checks); |
| } |
| |
| |
| hash_destroy(hash_tbl); |
| MemoryContextDelete(context); |
| heap_close(pgcon, AccessShareLock); |
| |
| return validation_list; |
| } |
| |
| /* |
| * Return a string of comma-delimited names of the constraints in the |
| * argument list of pg_constraint tuples. This is primarily for use |
| * in messages. |
| */ |
| static char * |
| constraint_names(List *cons) |
| { |
| HeapTuple tuple; |
| Form_pg_constraint con; |
| ListCell *lc; |
| StringInfoData str; |
| |
| initStringInfo(&str); |
| |
| char *p = ""; |
| foreach (lc, cons) |
| { |
| tuple = linitial(cons); |
| con = (Form_pg_constraint) GETSTRUCT(tuple); |
| appendStringInfo(&str, "%s\"%s\"", p, NameStr(con->conname)); |
| p = ", "; |
| } |
| |
| return str.data; |
| } |
| |
| |
| /* |
| * Identify constraints in the first list that don't correspond to |
| * constraints in the second (missing) and vice versa (extra). Assume |
| * that the constraints all match semantically, i.e, their expressions |
| * are equivalent. (There are no checks for this.) Also assume there |
| * are no duplicate constraint names in either argument list. (This |
| * isn't checked, but is asserted. |
| * |
| * There are two checking modes. One matches by name, one prefers |
| * matching names, but accepts mismatches based on the assumed |
| * semantic equivlence. |
| * |
| * Matching by name (which applies to regular constraints on the whole |
| * table) may have missing and/or extra constraints. |
| * |
| * Matching by expression (which applies to partition constraints) |
| * can yield only one or the other of extra or missing constraints. |
| * The cleverness is that we match by name to avoid choosing as the |
| * missing or extra constraints ones that match by name with items in |
| * the other list. |
| */ |
| static void |
| constraint_diffs(List *cons_a, List *cons_b, bool match_names, List **missing, List **extra) |
| { |
| ListCell *cell_a, *cell_b; |
| Index pos_a, pos_b; |
| int *match_a, *match_b; |
| int n; |
| |
| int len_a = list_length(cons_a); |
| int len_b = list_length(cons_b); |
| |
| Assert(missing != NULL); |
| Assert(extra != NULL); |
| |
| if ( len_a == 0 ) |
| { |
| *extra = list_copy(cons_b); |
| *missing = NIL; |
| return; |
| } |
| |
| if ( len_b == 0 ) |
| { |
| *extra = NIL; |
| *missing = list_copy(cons_a); |
| return; |
| } |
| |
| match_a = (int*)palloc(len_a * sizeof(int)); |
| for ( pos_a = 0; pos_a < len_a; pos_a++ ) |
| match_a[pos_a] = -1; |
| |
| match_b = (int*)palloc(len_b * sizeof(int)); |
| for ( pos_b = 0; pos_b < len_b; pos_b++ ) |
| match_b[pos_b] = -1; |
| |
| pos_b = 0; |
| foreach (cell_b, cons_b) |
| { |
| HeapTuple tuple_b = (HeapTuple)lfirst(cell_b); |
| Form_pg_constraint b = (Form_pg_constraint) GETSTRUCT(tuple_b); |
| |
| |
| pos_a = 0; |
| foreach (cell_a, cons_a) |
| { |
| HeapTuple tuple_a = lfirst(cell_a); |
| Form_pg_constraint a = (Form_pg_constraint) GETSTRUCT(tuple_a); |
| |
| if ( strncmp(NameStr(a->conname), NameStr(b->conname), NAMEDATALEN) == 0 ) |
| { |
| /* No duplicate names on either list. */ |
| Assert(match_a[pos_a] == -1 && match_b[pos_b] == -1); |
| |
| match_b[pos_b] = pos_a; |
| match_a[pos_a] = pos_b; |
| break; |
| } |
| pos_a++; |
| } |
| pos_b++; |
| } |
| |
| *missing = NIL; |
| *extra = NIL; |
| |
| n = len_a - len_b; |
| if ( n > 0 || match_names ) |
| { |
| pos_a = 0; |
| foreach (cell_a, cons_a) |
| { |
| if ( match_a[pos_a] == -1 ) |
| *missing = lappend(*missing, lfirst(cell_a)); |
| pos_a++; |
| n--; |
| if ( n <= 0 && !match_names) |
| break; |
| } |
| } |
| |
| n = len_b - len_a; |
| if ( n > 0 || match_names ) |
| { |
| pos_b = 0; |
| foreach (cell_b, cons_b) |
| { |
| if ( match_b[pos_b] == -1 ) |
| *extra = lappend(*extra, lfirst(cell_b)); |
| pos_b++; |
| n--; |
| if ( n <= 0 && !match_names ) |
| break; |
| } |
| } |
| |
| pfree(match_a); |
| pfree(match_b); |
| } |
| |
| |
| |
| /* |
| * Install a dependency of a regular constraint on a part of a |
| * partitioned to on the corresponding regular constaint on the |
| * partitioned table as a whole. |
| * |
| * NOTE This doesn't apply to partition constraints that guard |
| * the partitioning policy of the table. These aren't represented |
| * on the partitioned table as a whole. |
| */ |
| |
| void |
| add_reg_part_dependency(Oid tableconid, Oid partconid) |
| { |
| ObjectAddress tablecon; |
| ObjectAddress partcon; |
| |
| tablecon.classId = ConstraintRelationId; |
| tablecon.objectId = tableconid; |
| tablecon.objectSubId = 0; |
| partcon.classId = ConstraintRelationId; |
| partcon.objectId = partconid; |
| partcon.objectSubId = 0; |
| recordDependencyOn(&partcon, &tablecon, DEPENDENCY_INTERNAL); |
| } |
| |
| |
| |
| /* |
| * Translate internal representation to catalog partition type indication |
| * ('r', 'h' or 'l'). |
| */ |
| static char |
| parttype_to_char(PartitionByType type) |
| { |
| char c; |
| |
| switch (type) |
| { |
| case PARTTYP_HASH: c = 'h'; break; |
| case PARTTYP_RANGE: c = 'r'; break; |
| case PARTTYP_LIST: c = 'l'; break; |
| default: |
| c = 0; /* quieten compiler */ |
| elog(ERROR, "unknown partitioning type %i", type); |
| |
| } |
| return c; |
| } |
| |
| /* |
| * Translate a catalog partition type indication ('r', 'h' or 'l') to the |
| * internal representation. |
| */ |
| PartitionByType |
| char_to_parttype(char c) |
| { |
| PartitionByType pt = PARTTYP_RANGE; /* just to shut GCC up */ |
| |
| switch(c) |
| { |
| case 'h': /* hash */ |
| pt = PARTTYP_HASH; |
| break; |
| case 'r': /* range */ |
| pt = PARTTYP_RANGE; |
| break; |
| case 'l': /* list */ |
| pt = PARTTYP_LIST; |
| break; |
| default: |
| elog(ERROR, "unrecognized partitioning kind '%c'", |
| c); |
| Assert(false); |
| break; |
| } /* end switch */ |
| |
| return pt; |
| } |
| |
| /* |
| * Add metadata for a partition level. |
| */ |
| static void |
| add_partition(Partition *part) |
| { |
| Datum values[Natts_pg_partition]; |
| bool isnull[Natts_pg_partition]; |
| Relation partrel; |
| HeapTuple tup; |
| oidvector *opclass; |
| int2vector *attnums; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| MemSet(isnull, 0, sizeof(bool) * Natts_pg_partition); |
| |
| values[Anum_pg_partition_parrelid - 1] = ObjectIdGetDatum(part->parrelid); |
| values[Anum_pg_partition_parkind - 1] = CharGetDatum(part->parkind); |
| values[Anum_pg_partition_parlevel - 1] = Int16GetDatum(part->parlevel); |
| values[Anum_pg_partition_paristemplate - 1] = |
| BoolGetDatum(part->paristemplate); |
| values[Anum_pg_partition_parnatts - 1] = Int16GetDatum(part->parnatts); |
| |
| attnums = buildint2vector(part->paratts, part->parnatts); |
| values[Anum_pg_partition_paratts - 1] = PointerGetDatum(attnums); |
| |
| opclass = buildoidvector(part->parclass, part->parnatts); |
| values[Anum_pg_partition_parclass - 1] = PointerGetDatum(opclass); |
| |
| partrel = heap_open(PartitionRelationId, RowExclusiveLock); |
| |
| pcqCtx = |
| caql_beginscan( |
| caql_addrel(cqclr(&cqc), partrel), |
| cql("INSERT INTO pg_partition ", |
| NULL)); |
| |
| tup = caql_form_tuple(pcqCtx, values, isnull); |
| |
| /* Insert tuple into the relation */ |
| part->partid = caql_insert(pcqCtx, tup); |
| /* and Update indexes (implicit) */ |
| |
| caql_endscan(pcqCtx); |
| heap_close(partrel, NoLock); |
| } |
| |
| /* |
| * Add a partition rule. A partition rule represents a discrete partition |
| * child. |
| */ |
| static void |
| add_partition_rule(PartitionRule *rule) |
| { |
| Datum values[Natts_pg_partition_rule]; |
| bool isnull[Natts_pg_partition_rule]; |
| Relation rulerel; |
| HeapTuple tup; |
| NameData name; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| MemSet(isnull, 0, sizeof(bool) * Natts_pg_partition_rule); |
| |
| values[Anum_pg_partition_rule_paroid - 1] = ObjectIdGetDatum(rule->paroid); |
| values[Anum_pg_partition_rule_parchildrelid - 1] = |
| ObjectIdGetDatum(rule->parchildrelid); |
| values[Anum_pg_partition_rule_parparentrule - 1] = |
| ObjectIdGetDatum(rule->parparentoid); |
| |
| name.data[0] = '\0'; |
| namestrcpy(&name, rule->parname); |
| values[Anum_pg_partition_rule_parname - 1] = NameGetDatum(&name); |
| |
| values[Anum_pg_partition_rule_parisdefault - 1] = |
| BoolGetDatum(rule->parisdefault); |
| values[Anum_pg_partition_rule_parruleord - 1] = |
| Int16GetDatum(rule->parruleord); |
| values[Anum_pg_partition_rule_parrangestartincl - 1] = |
| BoolGetDatum(rule->parrangestartincl); |
| values[Anum_pg_partition_rule_parrangeendincl - 1] = |
| BoolGetDatum(rule->parrangeendincl); |
| |
| values[Anum_pg_partition_rule_parrangestart - 1] = |
| DirectFunctionCall1(textin, |
| CStringGetDatum(nodeToString(rule->parrangestart))); |
| values[Anum_pg_partition_rule_parrangeend - 1] = |
| DirectFunctionCall1(textin, |
| CStringGetDatum(nodeToString(rule->parrangeend))); |
| values[Anum_pg_partition_rule_parrangeevery - 1] = |
| DirectFunctionCall1(textin, |
| CStringGetDatum(nodeToString(rule->parrangeevery))); |
| values[Anum_pg_partition_rule_parlistvalues - 1] = |
| DirectFunctionCall1(textin, |
| CStringGetDatum(nodeToString(rule->parlistvalues))); |
| if (rule->parreloptions) |
| values[Anum_pg_partition_rule_parreloptions - 1] = |
| transformRelOptions((Datum) 0, rule->parreloptions, true, false); |
| else |
| isnull[Anum_pg_partition_rule_parreloptions - 1] = true; |
| |
| values[Anum_pg_partition_rule_partemplatespace -1] = |
| ObjectIdGetDatum(rule->partemplatespaceId); |
| |
| rulerel = heap_open(PartitionRuleRelationId, RowExclusiveLock); |
| |
| pcqCtx = |
| caql_beginscan( |
| caql_addrel(cqclr(&cqc), rulerel), |
| cql("INSERT INTO pg_partition_rule ", |
| NULL)); |
| |
| tup = caql_form_tuple(pcqCtx, values, isnull); |
| |
| /* Insert tuple into the relation */ |
| rule->parruleid = caql_insert(pcqCtx, tup); |
| /* and Update indexes (implicit) */ |
| |
| caql_endscan(pcqCtx); |
| heap_close(rulerel, NoLock); |
| } |
| |
| /* |
| * Oid of the row of pg_partition corresponding to the given relation and level. |
| */ |
| static Oid |
| get_part_oid(Oid rootrelid, int16 parlevel, bool istemplate) |
| { |
| Oid paroid = InvalidOid; |
| cqContext cqc; |
| |
| /* select oid |
| * from pg_partition |
| * where |
| * parrelid = :rootrelid and |
| * parlevel = :parlevel and |
| * paristemplate = :istemplate; |
| */ |
| Relation rel = heap_open(PartitionRelationId, AccessShareLock); |
| |
| paroid = caql_getoid( |
| caql_snapshot(caql_addrel(cqclr(&cqc), rel), |
| SnapshotSelf), /* XXX XXX: SnapshotSelf */ |
| cql("SELECT oid FROM pg_partition " |
| " WHERE parrelid = :1 " |
| " AND parlevel = :2 " |
| " AND paristemplate = :3", |
| ObjectIdGetDatum(rootrelid), |
| Int16GetDatum(parlevel), |
| Int16GetDatum(istemplate))); |
| |
| heap_close(rel, NoLock); |
| |
| return paroid; |
| } |
| |
| /* |
| * delete the template for a partition |
| */ |
| int |
| del_part_template(Oid rootrelid, int16 parlevel, Oid parent) |
| { |
| bool istemplate = true; |
| Oid paroid = InvalidOid; |
| int ii = 0; |
| int fetchCount; |
| |
| paroid = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oid FROM pg_partition " |
| " WHERE parrelid = :1 " |
| " AND parlevel = :2 " |
| " AND paristemplate = :3 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(rootrelid), |
| Int16GetDatum(parlevel), |
| Int16GetDatum(istemplate)) |
| ); |
| |
| if (0 == fetchCount) |
| return 0; |
| |
| /* should only be one matching template per level */ |
| if (fetchCount > 1) |
| return 2; |
| |
| ii = caql_getcount( |
| NULL, |
| cql("DELETE FROM pg_partition_rule " |
| " WHERE paroid = :1 " |
| " AND parparentrule = :2 ", |
| ObjectIdGetDatum(paroid), |
| ObjectIdGetDatum(parent)) |
| ); |
| |
| /* now delete the pg_partition entry */ |
| |
| ii = caql_getcount( |
| NULL, |
| cql("DELETE FROM pg_partition " |
| " WHERE parrelid = :1 " |
| " AND parlevel = :2 " |
| " AND paristemplate = :3 ", |
| ObjectIdGetDatum(rootrelid), |
| Int16GetDatum(parlevel), |
| Int16GetDatum(istemplate)) |
| ); |
| |
| /* make visible */ |
| CommandCounterIncrement(); |
| |
| return 1; |
| } /* end del_part_template */ |
| |
| |
| /* |
| * add_part_to_catalog() - add a partition to the catalog |
| * |
| * |
| |
| * NOTE: If bTemplate_Only = false, add both actual partitions and the |
| * template definitions (if specified). However, if bTemplate_Only = |
| * true, then only treat the partition spec as a template. |
| */ |
| void |
| add_part_to_catalog(Oid relid, PartitionBy *pby, |
| bool bTemplate_Only /* = false */) |
| { |
| char pt = parttype_to_char(pby->partType); |
| ListCell *lc; |
| PartitionSpec *spec; |
| Oid paroid = InvalidOid; |
| Oid rootrelid = InvalidOid; |
| Relation rel; |
| Oid parttemplid = InvalidOid; |
| bool add_temp = bTemplate_Only; /* normally false */ |
| spec = (PartitionSpec *)pby->partSpec; |
| |
| /* only create partition catalog entries on the master */ |
| if (Gp_role == GP_ROLE_EXECUTE) |
| return; |
| |
| /* |
| * Get the partitioned table relid. |
| */ |
| rootrelid = RangeVarGetRelid(pby->parentRel, false, false /*allowHcatalog*/); |
| paroid = get_part_oid(rootrelid, pby->partDepth, |
| bTemplate_Only /* = false */); |
| |
| /* create a partition for this level, if one doesn't exist */ |
| if (!OidIsValid(paroid)) |
| { |
| AttrNumber *attnums; |
| Oid *parclass; |
| Partition *part = makeNode(Partition); |
| int i = 0; |
| |
| part->parrelid = rootrelid; |
| part->parkind = pt; |
| part->parlevel = pby->partDepth; |
| |
| if (pby->partSpec) |
| part->paristemplate = ((PartitionSpec *)pby->partSpec)->istemplate; |
| else |
| part->paristemplate = false; |
| |
| part->parnatts = list_length(pby->keys); |
| |
| attnums = palloc(list_length(pby->keys) * sizeof(AttrNumber)); |
| |
| foreach(lc, pby->keys) |
| { |
| int colnum = lfirst_int(lc); |
| |
| attnums[i++] = colnum; |
| } |
| |
| part->paratts = attnums; |
| |
| parclass = palloc(list_length(pby->keys) * sizeof(Oid)); |
| |
| i = 0; |
| foreach(lc, pby->keyopclass) |
| { |
| Oid opclass = lfirst_oid(lc); |
| |
| parclass[i++] = opclass; |
| } |
| part->parclass = parclass; |
| |
| add_partition(part); |
| |
| /* |
| * If we added a template, we treat that as a 'virtual' entry and then |
| * add a modifiable entry, which is not a template. |
| */ |
| if (part->paristemplate) |
| { |
| add_temp = true; |
| parttemplid = part->partid; |
| |
| if (spec && spec->enc_clauses) |
| { |
| add_template_encoding_clauses(relid, parttemplid, |
| spec->enc_clauses); |
| } |
| |
| /* if only building a template, don't add "real" entries */ |
| if (!bTemplate_Only) |
| { |
| part->paristemplate = false; |
| add_partition(part); |
| |
| } |
| } |
| paroid = part->partid; |
| } |
| else |
| /* oid of the template accompanying the real partition */ |
| parttemplid = get_part_oid(rootrelid, pby->partDepth, true); |
| |
| /* create partition rule */ |
| if (spec) |
| { |
| Node *listvalues = NULL; |
| Node *rangestart = NULL; |
| Node *rangeend = NULL; |
| Node *rangeevery = NULL; |
| bool rangestartinc = false; |
| bool rangeendinc = false; |
| int2 parruleord = 0; |
| PartitionRule *rule = makeNode(PartitionRule); |
| PartitionElem *el; |
| char *parname = NULL; |
| Oid parentoid = InvalidOid; |
| |
| Assert(list_length(spec->partElem) == 1); |
| |
| el = linitial(spec->partElem); |
| |
| parruleord = el->partno; |
| |
| if (el->partName) |
| parname = strVal(el->partName); |
| |
| switch (pby->partType) |
| { |
| case PARTTYP_HASH: break; |
| case PARTTYP_LIST: |
| { |
| PartitionValuesSpec *vspec = |
| (PartitionValuesSpec *)el->boundSpec; |
| |
| /* might be NULL if this is a default spec */ |
| if (vspec) |
| listvalues = (Node *)vspec->partValues; |
| } |
| break; |
| case PARTTYP_RANGE: |
| { |
| PartitionBoundSpec *bspec = |
| (PartitionBoundSpec *)el->boundSpec; |
| PartitionRangeItem *ri; |
| |
| /* remember, could be a default clause */ |
| if (bspec) |
| { |
| Assert(IsA(bspec, PartitionBoundSpec)); |
| ri = (PartitionRangeItem *)bspec->partStart; |
| if (ri) |
| { |
| Assert(ri->partedge == PART_EDGE_INCLUSIVE || |
| ri->partedge == PART_EDGE_EXCLUSIVE); |
| |
| rangestartinc = ri->partedge == PART_EDGE_INCLUSIVE; |
| rangestart = (Node *)ri->partRangeVal; |
| } |
| |
| ri = (PartitionRangeItem *)bspec->partEnd; |
| if (ri) |
| { |
| Assert(ri->partedge == PART_EDGE_INCLUSIVE || |
| ri->partedge == PART_EDGE_EXCLUSIVE); |
| |
| rangeendinc = ri->partedge == PART_EDGE_INCLUSIVE; |
| rangeend = (Node *)ri->partRangeVal; |
| } |
| |
| if (bspec->partEvery) |
| { |
| ri = (PartitionRangeItem *)bspec->partEvery; |
| rangeevery = (Node *)ri->partRangeVal; |
| } |
| else |
| rangeevery = NULL; |
| } |
| } |
| break; |
| default: |
| elog(ERROR, "unknown partitioning type %i", pby->partType); |
| break; |
| } |
| |
| /* Find our parent */ |
| if (!bTemplate_Only && (pby->partDepth > 0)) |
| { |
| Oid inhoid; |
| cqContext cqc; |
| int fetchCount = 0; |
| |
| rel = heap_open(InheritsRelationId, AccessShareLock); |
| |
| inhoid = caql_getoid_plus( |
| caql_snapshot(caql_addrel(cqclr(&cqc), rel), |
| SnapshotAny), /* XXX XXX: SnapshotAny */ |
| &fetchCount, |
| NULL, |
| cql("SELECT inhparent FROM pg_inherits " |
| " WHERE inhrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| Assert(fetchCount > 0); |
| |
| heap_close(rel, NoLock); |
| |
| rel = heap_open(PartitionRuleRelationId, AccessShareLock); |
| |
| parentoid = caql_getoid_plus( |
| caql_snapshot(caql_addrel(cqclr(&cqc), rel), |
| SnapshotAny), /* XXX XXX: SnapshotAny */ |
| &fetchCount, |
| NULL, |
| cql("SELECT oid FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(inhoid))); |
| |
| Assert(fetchCount > 0); |
| |
| heap_close(rel, NoLock); |
| } |
| else |
| add_temp = true; |
| |
| /* we still might have to add template rules */ |
| if (!add_temp && OidIsValid(parttemplid)) |
| { |
| Oid pcr; |
| cqContext cqc; |
| int fetchCount = 0; |
| |
| pcr = caql_getoid_plus( |
| caql_snapshot(cqclr(&cqc), |
| SnapshotAny), /* XXX XXX: SnapshotAny */ |
| &fetchCount, |
| NULL, |
| cql("SELECT parchildrelid FROM pg_partition_rule " |
| " WHERE paroid = :1 " |
| " AND parparentrule = :2 " |
| " AND parruleord = :3 ", |
| ObjectIdGetDatum(parttemplid), |
| ObjectIdGetDatum(InvalidOid), |
| Int16GetDatum(parruleord))); |
| |
| if (fetchCount) |
| Assert(pcr == InvalidOid); |
| else |
| add_temp = true; |
| |
| } |
| |
| rule->paroid = paroid; |
| rule->parchildrelid = relid; |
| rule->parparentoid = parentoid; |
| rule->parisdefault = el->isDefault; |
| rule->parname = parname; |
| rule->parruleord = parruleord; |
| rule->parrangestartincl = rangestartinc; |
| rule->parrangestart = rangestart; |
| rule->parrangeendincl = rangeendinc; |
| rule->parrangeend = rangeend; |
| rule->parrangeevery = rangeevery; |
| rule->parlistvalues = (List *)listvalues; |
| rule->partemplatespaceId = InvalidOid; /* only valid for template */ |
| |
| if (!bTemplate_Only) |
| add_partition_rule(rule); |
| |
| if (OidIsValid(parttemplid) && add_temp) |
| { |
| rule->paroid = parttemplid; |
| rule->parparentoid = InvalidOid; |
| rule->parchildrelid = InvalidOid; |
| |
| if (el->storeAttr) |
| { |
| if (((AlterPartitionCmd *)el->storeAttr)->arg1) |
| rule->parreloptions = |
| (List *)((AlterPartitionCmd *)el->storeAttr)->arg1; |
| if (((AlterPartitionCmd *)el->storeAttr)->arg2) |
| { |
| Oid tablespaceId; |
| |
| tablespaceId = |
| get_settable_tablespace_oid( |
| strVal(((AlterPartitionCmd *)el->storeAttr)->arg2)); |
| |
| /* get_settable_tablespace_oid will error out for us */ |
| Assert(OidIsValid(tablespaceId)); |
| |
| /* only valid for template definitions */ |
| rule->partemplatespaceId = tablespaceId; |
| } |
| } |
| add_partition_rule(rule); |
| } |
| } |
| |
| /* allow subsequent callers to see our work */ |
| CommandCounterIncrement(); |
| } /* end add_part_to_catalog */ |
| |
| |
| /* |
| * parruleord_reset_rank |
| * |
| * iterate over the specified set of range partitions (in ascending |
| * order) in pg_partition_rule and reset the parruleord to start at 1 |
| * and continue in an ascending sequence. |
| */ |
| void |
| parruleord_reset_rank(Oid partid, int2 level, Oid parent, int2 ruleord, |
| MemoryContext mcxt) |
| { |
| ScanKeyData key[3]; |
| HeapTuple tuple; |
| Relation rel; |
| SysScanDesc scan; |
| int ii = 1; |
| |
| rel = heap_open(PartitionRuleRelationId, AccessShareLock); |
| |
| /* CaQL UNDONE: no test coverage; this function is not called at all */ |
| ScanKeyInit(&key[0], |
| Anum_pg_partition_rule_paroid, |
| BTEqualStrategyNumber, F_OIDEQ, |
| ObjectIdGetDatum(partid)); |
| ScanKeyInit(&key[1], |
| Anum_pg_partition_rule_parparentrule, |
| BTEqualStrategyNumber, F_INT2EQ, |
| ObjectIdGetDatum(parent)); |
| ScanKeyInit(&key[2], |
| Anum_pg_partition_rule_parruleord, |
| BTGreaterEqualStrategyNumber, F_INT2GE, |
| Int16GetDatum(ruleord)); |
| |
| scan = systable_beginscan(rel, |
| PartitionRuleParoidParparentruleParruleordIndexId, |
| true, SnapshotNow, 3, key); |
| |
| while ((tuple = systable_getnext(scan))) |
| { |
| Form_pg_partition_rule rule_desc; |
| |
| Insist(HeapTupleIsValid(tuple)); |
| |
| tuple = heap_copytuple(tuple); |
| |
| rule_desc = |
| (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| rule_desc->parruleord = ii; |
| ii++; |
| |
| simple_heap_update(rel, &tuple->t_self, tuple); |
| CatalogUpdateIndexes(rel, tuple); |
| |
| } |
| systable_endscan(scan); |
| heap_close(rel, AccessShareLock); |
| } /* end parruleord_reset_rank */ |
| |
| /* |
| * parruleord_open_gap |
| * |
| * iterate over the specified set of range partitions (in *DESCENDING* |
| * order) in pg_partition_rule and increment the parruleord in order |
| * to open a "gap" for a new partition. The stopkey is inclusive: to |
| * insert a new partition at parruleord=5, set the stopkey to 5. The |
| * current partition at parruleord=5 (and all subsequent partitions) |
| * are incremented by 1 to allow the insertion of the new partition. |
| * |
| */ |
| void |
| parruleord_open_gap(Oid partid, int2 level, Oid parent, int2 ruleord, |
| int stopkey, |
| MemoryContext mcxt) |
| { |
| HeapTuple tuple; |
| cqContext *pcqCtx; |
| |
| /* XXX XXX: should be an ORDER BY DESC */ |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE paroid = :1 " |
| " AND parparentrule = :2 " |
| " AND parruleord <= :3 " |
| " ORDER BY paroid, parparentrule, parruleord " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(partid), |
| ObjectIdGetDatum(parent), |
| Int16GetDatum(ruleord))); |
| |
| while (HeapTupleIsValid(tuple = caql_getprev(pcqCtx))) |
| { |
| int old_ruleord; |
| Form_pg_partition_rule rule_desc; |
| |
| Insist(HeapTupleIsValid(tuple)); |
| |
| tuple = heap_copytuple(tuple); |
| |
| rule_desc = |
| (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| old_ruleord = rule_desc->parruleord; |
| rule_desc->parruleord++; |
| |
| caql_update_current(pcqCtx, tuple); |
| |
| if (old_ruleord <= stopkey) |
| break; |
| } |
| caql_endscan(pcqCtx); |
| |
| } /* end parruleord_open_gap */ |
| |
| /* |
| * Build up a PartitionRule based on a tuple from pg_partition_rule |
| * Exported for ruleutils.c |
| */ |
| PartitionRule * |
| ruleMakePartitionRule(HeapTuple tuple, TupleDesc tupdesc, MemoryContext mcxt) |
| { |
| Form_pg_partition_rule rule_desc = |
| (Form_pg_partition_rule)GETSTRUCT(tuple); |
| text *rule_text; |
| char *rule_str; |
| Datum rule_datum; |
| bool isnull; |
| MemoryContext oldcxt; |
| PartitionRule *rule; |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rule = makeNode(PartitionRule); |
| MemoryContextSwitchTo(oldcxt); |
| |
| rule->parruleid = HeapTupleGetOid(tuple); |
| rule->paroid = rule_desc->paroid; |
| rule->parchildrelid = rule_desc->parchildrelid; |
| rule->parparentoid = rule_desc->parparentrule; |
| rule->parisdefault = rule_desc->parisdefault; |
| |
| rule->parname = MemoryContextStrdup(mcxt, NameStr(rule_desc->parname)); |
| |
| rule->parruleord = rule_desc->parruleord; |
| rule->parrangestartincl = rule_desc->parrangestartincl; |
| rule->parrangeendincl = rule_desc->parrangeendincl; |
| |
| /* start range */ |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_parrangestart, |
| tupdesc, |
| &isnull); |
| Assert(!isnull); |
| rule_text = DatumGetTextP(rule_datum); |
| rule_str = DatumGetCString(DirectFunctionCall1(textout, |
| PointerGetDatum(rule_text))); |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rule->parrangestart = stringToNode(rule_str); |
| MemoryContextSwitchTo(oldcxt); |
| |
| pfree(rule_str); |
| |
| /* end range */ |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_parrangeend, |
| tupdesc, |
| &isnull); |
| Assert(!isnull); |
| rule_text = DatumGetTextP(rule_datum); |
| rule_str = DatumGetCString(DirectFunctionCall1(textout, |
| PointerGetDatum(rule_text))); |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rule->parrangeend = stringToNode(rule_str); |
| MemoryContextSwitchTo(oldcxt); |
| |
| pfree(rule_str); |
| |
| /* every */ |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_parrangeevery, |
| tupdesc, |
| &isnull); |
| Assert(!isnull); |
| rule_text = DatumGetTextP(rule_datum); |
| rule_str = DatumGetCString(DirectFunctionCall1(textout, |
| PointerGetDatum(rule_text))); |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rule->parrangeevery = stringToNode(rule_str); |
| MemoryContextSwitchTo(oldcxt); |
| |
| /* list values */ |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_parlistvalues, |
| tupdesc, |
| &isnull); |
| Assert(!isnull); |
| rule_text = DatumGetTextP(rule_datum); |
| rule_str = DatumGetCString(DirectFunctionCall1(textout, |
| PointerGetDatum(rule_text))); |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rule->parlistvalues = stringToNode(rule_str); |
| MemoryContextSwitchTo(oldcxt); |
| |
| pfree(rule_str); |
| |
| if (rule->parlistvalues) |
| Assert(IsA(rule->parlistvalues, List)); |
| |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_parreloptions, |
| tupdesc, |
| &isnull); |
| |
| if (isnull) |
| rule->parreloptions = NIL; |
| else |
| { |
| ArrayType *array = DatumGetArrayTypeP(rule_datum); |
| Datum *options; |
| int noptions; |
| List *opts = NIL; |
| int i; |
| |
| /* XXX XXX: why not use untransformRelOptions ? */ |
| |
| Assert(ARR_ELEMTYPE(array) == TEXTOID); |
| |
| deconstruct_array(array, TEXTOID, -1, false, 'i', |
| &options, NULL, &noptions); |
| |
| /* key/value pairs for storage clause */ |
| for (i = 0; i < noptions; i++) |
| { |
| char *n = DatumGetCString(DirectFunctionCall1(textout, options[i])); |
| Value *v = NULL; |
| char *s; |
| |
| s = strchr(n, '='); |
| |
| if (s) |
| { |
| *s = '\0'; |
| s++; |
| if (*s) |
| v = makeString(s); |
| } |
| |
| opts = lappend(opts, makeDefElem(n, (Node *)v)); |
| } |
| rule->parreloptions = opts; |
| |
| } |
| |
| rule_datum = heap_getattr(tuple, |
| Anum_pg_partition_rule_partemplatespace, |
| tupdesc, |
| &isnull); |
| if (isnull) |
| rule->partemplatespaceId = InvalidOid; |
| else |
| rule->partemplatespaceId = DatumGetObjectId(rule_datum); |
| |
| return rule; |
| } |
| |
| /* |
| * Construct a Partition node from a pg_partition tuple and its description. |
| * Result is in the given memory context. |
| */ |
| Partition * |
| partMakePartition(HeapTuple tuple, TupleDesc tupdesc, MemoryContext mcxt) |
| { |
| oidvector *oids; |
| int2vector *atts; |
| bool isnull; |
| Form_pg_partition partrow = (Form_pg_partition)GETSTRUCT(tuple); |
| MemoryContext oldcxt; |
| Partition *p; |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| p = makeNode(Partition); |
| MemoryContextSwitchTo(oldcxt); |
| |
| p->partid = HeapTupleGetOid(tuple); |
| p->parrelid = partrow->parrelid; |
| p->parkind = partrow->parkind; |
| p->parlevel = partrow->parlevel; |
| p->paristemplate = partrow->paristemplate; |
| p->parnatts = partrow->parnatts; |
| |
| atts = DatumGetPointer(heap_getattr(tuple, Anum_pg_partition_paratts, |
| tupdesc, &isnull)); |
| Assert(!isnull); |
| oids = DatumGetPointer(heap_getattr(tuple, Anum_pg_partition_parclass, |
| tupdesc, &isnull)); |
| Assert(!isnull); |
| |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| p->paratts = palloc(sizeof(int2) * p->parnatts); |
| p->parclass = palloc(sizeof(Oid) * p->parnatts); |
| MemoryContextSwitchTo(oldcxt); |
| |
| memcpy(p->paratts, atts->values, sizeof(int2) * p->parnatts); |
| memcpy(p->parclass, oids->values, sizeof(Oid) * p->parnatts); |
| |
| return p; |
| } |
| |
| /* |
| * Construct a PartitionNode-PartitionRule tree for the given part. |
| * Recurs to contruct branches. Note that the ParitionRule (and, |
| * hence, the Oid) of the given part itself is not included in the |
| * result. |
| * |
| * relid -- pg_class.oid of the partitioned table |
| * level -- partitioning level |
| * parent -- pg_partition_rule.oid of the parent of |
| * inctemplate -- should the tree include template rules? |
| * mcxt -- memory context |
| * includesubparts -- whether or not to include sub partitions |
| */ |
| PartitionNode * |
| get_parts(Oid relid, int2 level, Oid parent, bool inctemplate, |
| MemoryContext mcxt, bool includesubparts) |
| { |
| PartitionNode *pnode = NULL; |
| MemoryContext oldcxt; |
| HeapTuple tuple; |
| Relation rel; |
| List *rules = NIL; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| |
| /* select oid as partid, * |
| * from pg_partition |
| * where |
| * parrelid = :relid and |
| * parlevel = :level and |
| * paristemplate = :inctemplate; |
| */ |
| rel = heap_open(PartitionRelationId, AccessShareLock); |
| |
| tuple = caql_getfirst( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 " |
| " AND parlevel = :2 " |
| " AND paristemplate = :3 ", |
| ObjectIdGetDatum(relid), |
| Int16GetDatum(level), |
| BoolGetDatum(inctemplate))); |
| |
| if (HeapTupleIsValid(tuple)) |
| { |
| pnode = makeNode(PartitionNode); |
| pnode->part = partMakePartition(tuple, RelationGetDescr(rel), mcxt); |
| } |
| |
| heap_close(rel, AccessShareLock); |
| |
| if ( ! pnode ) |
| return pnode; |
| |
| /* select * |
| * from pg_partition_rule |
| * where |
| * paroid = :pnode->part->partid and -- pg_partition.oid |
| * parparentrule = :parent; |
| */ |
| rel = heap_open(PartitionRuleRelationId, AccessShareLock); |
| |
| if (includesubparts) |
| { |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE paroid = :1 " |
| " AND parparentrule = :2 ", |
| ObjectIdGetDatum(pnode->part->partid), |
| ObjectIdGetDatum(parent))); |
| } |
| else |
| { |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE paroid = :1 ", |
| ObjectIdGetDatum(pnode->part->partid))); |
| } |
| |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| PartitionRule *rule; |
| |
| rule = ruleMakePartitionRule(tuple, RelationGetDescr(rel), mcxt); |
| if (includesubparts) |
| { |
| rule->children = get_parts(relid, level + 1, rule->parruleid, |
| inctemplate, mcxt, true /*includesubparts*/); |
| } |
| |
| if (rule->parisdefault) |
| pnode->default_part = rule; |
| else |
| { |
| oldcxt = MemoryContextSwitchTo(mcxt); |
| rules = lappend(rules, rule); |
| MemoryContextSwitchTo(oldcxt); |
| } |
| } |
| /* NOTE: this assert is valid, except for the case of splitting |
| * the very last partition of a table. For that case, we must |
| * drop the last partition before re-adding the new pieces, which |
| * violates this invariant |
| */ |
| /* Assert(inctemplate || list_length(rules) || pnode->default_part); */ |
| pnode->rules = rules; |
| |
| caql_endscan(pcqCtx); |
| heap_close(rel, AccessShareLock); |
| return pnode; |
| } |
| |
| PartitionNode * |
| RelationBuildPartitionDesc(Relation rel, bool inctemplate) |
| { |
| return RelationBuildPartitionDescByOid(RelationGetRelid(rel), inctemplate); |
| } |
| |
| PartitionNode * |
| RelationBuildPartitionDescByOid(Oid relid, bool inctemplate) |
| { |
| PartitionNode *n; |
| MemoryContext mcxt = CurrentMemoryContext; |
| |
| n = get_parts(relid, 0, 0, inctemplate, mcxt, true /*includesubparts*/); |
| |
| return n; |
| } |
| |
| /* |
| * Return a Bitmapset of the attribute numbers of a partitioned table |
| * (not a part). The attribute numbers refer to the root partition table. |
| * Call only on the entry database. Returns an empty set, if called on a |
| * regular table or a part. |
| * |
| * Note: Reads the pg_partition catalog. If you have a PartitionNode, |
| * in hand, use get_partition_attrs and construct a Bitmapset from its |
| * result instead. |
| */ |
| Bitmapset * |
| get_partition_key_bitmapset(Oid relid) |
| { |
| Relation rel; |
| HeapTuple tuple; |
| TupleDesc tupledesc; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| |
| Bitmapset *partition_key = NULL; |
| |
| /* select paratts |
| * from pg_partition |
| * where |
| * parrelid = :relid and |
| * not paristemplate; |
| */ |
| rel = heap_open(PartitionRelationId, AccessShareLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), rel), |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| /* XXX XXX: hmm, could we add |
| " AND paristemplate = :2 ", |
| ... |
| BoolGetDatum(false))); |
| |
| Could caql use an index on parrelid even though paristemplate |
| not in index? |
| */ |
| |
| tupledesc = RelationGetDescr(rel); |
| |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| int i; |
| int2 natts; |
| int2vector *atts; |
| bool isnull; |
| Form_pg_partition partrow = (Form_pg_partition)GETSTRUCT(tuple); |
| |
| if (partrow->paristemplate) |
| continue; /* no interest in template parts */ |
| |
| natts = partrow->parnatts; |
| atts = DatumGetPointer(heap_getattr(tuple, Anum_pg_partition_paratts, |
| tupledesc, &isnull)); |
| Insist(!isnull); |
| |
| for ( i = 0; i < natts; i++ ) |
| partition_key = bms_add_member(partition_key, atts->values[i]); |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(rel, AccessShareLock); |
| |
| return partition_key; |
| } |
| |
| |
| /* |
| * Return a list of partition attributes. Order is not guaranteed. |
| * Caller must free the result. |
| */ |
| List * |
| get_partition_attrs(PartitionNode *pn) |
| { |
| List *attrs = NIL; |
| int i; |
| |
| if (!pn) |
| return NIL; |
| |
| for (i = 0; i < pn->part->parnatts; i++) |
| attrs = lappend_int(attrs, pn->part->paratts[i]); |
| |
| /* We don't want duplicates, do just go down a single branch */ |
| if (list_length(pn->rules)) |
| { |
| PartitionRule *rule = linitial(pn->rules); |
| |
| return list_concat_unique_int(attrs, get_partition_attrs(rule->children)); |
| } |
| else |
| return attrs; |
| } |
| |
| void |
| partition_get_policies_attrs(PartitionNode *pn, GpPolicy *master_policy, |
| List **cols) |
| { |
| if (!pn) |
| return; |
| else |
| { |
| ListCell *lc; |
| |
| /* |
| * We use master_policy as a fast path. The assumption is that most |
| * child partitions look like the master so we don't want to enter |
| * the O(N^2) loop below if we can avoid it. Firstly, though, we must |
| * copy the master policy into the list. |
| */ |
| if (*cols == NIL && master_policy->nattrs) |
| { |
| int attno; |
| |
| for (attno = 0; attno < master_policy->nattrs; attno++) |
| *cols = lappend_int(*cols, master_policy->attrs[attno]); |
| } |
| |
| foreach(lc, pn->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| Relation rel = heap_open(rule->parchildrelid, NoLock); |
| |
| if (master_policy->nattrs != rel->rd_cdbpolicy->nattrs || |
| memcmp(master_policy->attrs, rel->rd_cdbpolicy->attrs, |
| (master_policy->nattrs * sizeof(AttrNumber)))) |
| { |
| int attno; |
| |
| for (attno = 0; attno < rel->rd_cdbpolicy->nattrs; attno++) |
| { |
| if (!list_member_int(*cols, |
| rel->rd_cdbpolicy->attrs[attno])) |
| *cols = lappend_int(*cols, |
| rel->rd_cdbpolicy->attrs[attno]); |
| } |
| } |
| heap_close(rel, NoLock); |
| |
| partition_get_policies_attrs(rule->children, master_policy, |
| cols); |
| } |
| } |
| } |
| |
| bool |
| partition_policies_equal(GpPolicy *p, PartitionNode *pn) |
| { |
| if (!pn) |
| return true; |
| |
| if (pn->rules) |
| { |
| ListCell *lc; |
| |
| foreach(lc, pn->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| Relation rel = heap_open(rule->parchildrelid, NoLock); |
| |
| if (p->nattrs != rel->rd_cdbpolicy->nattrs) |
| { |
| heap_close(rel, NoLock); |
| return false; |
| } |
| else |
| { |
| if (p->attrs == 0) |
| /* random policy, skip */ |
| ; |
| if (memcmp(p->attrs, rel->rd_cdbpolicy->attrs, |
| (sizeof(AttrNumber) * p->nattrs))) |
| { |
| heap_close(rel, NoLock); |
| return false; |
| } |
| } |
| if (!partition_policies_equal(p, rule->children)) |
| { |
| heap_close(rel, NoLock); |
| return false; |
| } |
| heap_close(rel, NoLock); |
| } |
| } |
| return true; |
| } |
| |
| AttrNumber |
| max_partition_attr(PartitionNode *pn) |
| { |
| AttrNumber n = 0; |
| List *l = get_partition_attrs(pn); |
| |
| if (l) |
| { |
| ListCell *lc; |
| |
| foreach(lc, l) |
| { |
| AttrNumber att = lfirst_int(lc); |
| |
| n = Max(att, n); |
| } |
| pfree(l); |
| } |
| return n; |
| } |
| |
| int |
| num_partition_levels(PartitionNode *pn) |
| { |
| PartitionNode *tmp; |
| int level = 0; |
| |
| tmp = pn; |
| |
| /* just descend one branch of the tree until we hit a leaf */ |
| while (tmp) |
| { |
| level++; |
| if (tmp->rules) |
| { |
| PartitionRule *rule = linitial(tmp->rules); |
| tmp = rule->children; |
| } |
| else if (tmp->default_part) |
| { |
| PartitionRule *rule = tmp->default_part; |
| tmp = rule->children; |
| } |
| else |
| tmp = NULL; |
| } |
| |
| return level; |
| } |
| |
| /* Return the pg_class Oids of the relations representing parts of the |
| * PartitionNode tree headed by the argument PartitionNode. |
| */ |
| List * |
| all_partition_relids(PartitionNode *pn) |
| { |
| if (!pn) |
| return NIL; |
| else |
| { |
| ListCell *lc; |
| List *out = NIL; |
| |
| foreach(lc, pn->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| |
| Assert(OidIsValid(rule->parchildrelid)); |
| out = lappend_oid(out, rule->parchildrelid); |
| |
| out = list_concat(out, all_partition_relids(rule->children)); |
| } |
| if (pn->default_part) |
| { |
| out = lappend_oid(out, pn->default_part->parchildrelid); |
| out = list_concat(out, |
| all_partition_relids(pn->default_part->children)); |
| } |
| return out; |
| } |
| } |
| |
| /* |
| * getPartConstraintsContainsKeys |
| * Given an OID, returns a Node that represents the check constraints |
| * on the table having constraint keys from the given key list. |
| * If there are multiple constraints they are AND'd together. |
| */ |
| static Node * |
| getPartConstraintsContainsKeys(Oid partOid, Oid rootOid, List *partKey) |
| { |
| cqContext *pcqCtx; |
| cqContext cqc; |
| Relation conRel; |
| HeapTuple conTup; |
| Node *conExpr; |
| Node *result = NULL; |
| Datum conBinDatum; |
| Datum conKeyDatum; |
| char *conBin; |
| bool conbinIsNull = false; |
| bool conKeyIsNull = false; |
| AttrMap *map; |
| |
| /* create the map needed for mapping attnums */ |
| Relation rootRel = heap_open(rootOid, AccessShareLock); |
| Relation partRel = heap_open(partOid, AccessShareLock); |
| |
| map_part_attrs(partRel, rootRel, &map, false); |
| |
| heap_close(rootRel, AccessShareLock); |
| heap_close(partRel, AccessShareLock); |
| |
| /* Fetch the pg_constraint row. */ |
| conRel = heap_open(ConstraintRelationId, AccessShareLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), conRel), |
| cql("SELECT * FROM pg_constraint " |
| " WHERE conrelid = :1", |
| ObjectIdGetDatum(partOid))); |
| |
| while (HeapTupleIsValid(conTup = caql_getnext(pcqCtx))) |
| { |
| /* we defer the filter on contype to here in order to take advantage of |
| * the index on conrelid in the above CAQL query */ |
| Form_pg_constraint conEntry = (Form_pg_constraint) GETSTRUCT(conTup); |
| if (conEntry->contype != 'c') |
| { |
| continue; |
| } |
| /* Fetch the constraint expression in parsetree form */ |
| conBinDatum = heap_getattr(conTup, Anum_pg_constraint_conbin, |
| RelationGetDescr(conRel), &conbinIsNull); |
| |
| Assert (!conbinIsNull); |
| /* map the attnums in constraint expression to root attnums */ |
| conBin = TextDatumGetCString(conBinDatum); |
| conExpr = stringToNode(conBin); |
| conExpr = attrMapExpr(map, conExpr); |
| |
| // fetch the key associated with this constraint |
| conKeyDatum = heap_getattr(conTup, Anum_pg_constraint_conkey, |
| RelationGetDescr(conRel), &conKeyIsNull); |
| Datum *dats = NULL; |
| int numKeys = 0; |
| |
| bool found = false; |
| // extract key elements |
| deconstruct_array(DatumGetArrayTypeP(conKeyDatum), INT2OID, 2, true, 's', &dats, NULL, &numKeys); |
| for (int i = 0; i < numKeys; i++) |
| { |
| int16 key_elem = DatumGetInt16(dats[i]); |
| if (list_find_int(partKey, key_elem) >= 0) |
| { |
| found = true; |
| break; |
| } |
| } |
| |
| if (found) |
| { |
| if (result) |
| result = (Node *)make_andclause(list_make2(result, conExpr)); |
| else |
| result = conExpr; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(conRel, AccessShareLock); |
| |
| return result; |
| } |
| |
| /* |
| * Create a hash table with both key and hash entry as a constraint Node* |
| * Input: |
| * nEntries - estimated number of elements in the hash table |
| * Outout: |
| * a pointer to the created hash table |
| */ |
| static HTAB* |
| createConstraintHashTable(unsigned int nEntries) |
| { |
| HASHCTL hash_ctl; |
| MemSet(&hash_ctl, 0, sizeof(hash_ctl)); |
| |
| hash_ctl.keysize = sizeof(Node**); |
| hash_ctl.entrysize = sizeof(ConNodeEntry); |
| hash_ctl.hash = constrNodeHash; |
| hash_ctl.match = constrNodeMatch; |
| |
| return hash_create("ConstraintHashTable", nEntries, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE); |
| } |
| |
| /* |
| * Hash function for a constraint node |
| * Input: |
| * keyPtr - pointer to hash key |
| * keysize - not used, hash function must have this signature |
| * Output: |
| * result - hash value as an unsigned integer |
| */ |
| static uint32 |
| constrNodeHash(const void *keyPtr, Size keysize) |
| { |
| uint32 result = 0; |
| Node *constr = *((Node **) keyPtr); |
| int con_len = 0; |
| if (constr) |
| { |
| char* constr_bin = nodeToBinaryStringFast(constr, &con_len); |
| Assert(con_len > 0); |
| result = tag_hash(constr_bin, con_len); |
| pfree(constr_bin); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Match function for two constraint nodes. |
| * Input: |
| * keyPtr1, keyPtr2 - pointers to two hash keys |
| * keysize - not used, hash function must have this signature |
| * Output: |
| * 0 if two hash keys match, 1 otherwise |
| */ |
| static int |
| constrNodeMatch(const void *keyPtr1, const void *keyPtr2, Size keysize) |
| { |
| Node *left = *((Node **) keyPtr1); |
| Node *right = *((Node **) keyPtr2); |
| return equal(left, right) ? 0 : 1; |
| } |
| |
| /* |
| * Check if a partitioning hierarchy is uniform, i.e. for each partitioning level, |
| * all the partition nodes should have the same number of children, AND the child nodes |
| * at the same position w.r.t the subtree should have the same constraint on the partition |
| * key of that level. |
| * The time complexity of this check is linear to the number of nodes in the partitioning |
| * hierarchy. |
| */ |
| bool |
| rel_partitioning_is_uniform(Oid rootOid) |
| { |
| Assert(OidIsValid(rootOid)); |
| Assert(rel_is_partitioned(rootOid)); |
| |
| bool result = true; |
| |
| MemoryContext uniformityMemoryContext = AllocSetContextCreate(CurrentMemoryContext, |
| "PartitioningIsUniform", |
| ALLOCSET_DEFAULT_MINSIZE, |
| ALLOCSET_DEFAULT_INITSIZE, |
| ALLOCSET_DEFAULT_MAXSIZE); |
| MemoryContext callerMemoryContext = MemoryContextSwitchTo(uniformityMemoryContext); |
| |
| PartitionNode *pnRoot = RelationBuildPartitionDescByOid(rootOid, false /*inctemplate*/); |
| List *queue = list_make1(pnRoot); |
| |
| while (result) |
| { |
| /* we process the partitioning tree level by level, each outer loop corresponds to one level */ |
| int size = list_length(queue); |
| if (0 == size) |
| { |
| break; |
| } |
| |
| /* Look ahead to get the number of children of the first partition node in this level. |
| * This allows us to initialize a hash table on the constraints which each partition node |
| * in this level will be compared to. |
| */ |
| PartitionNode *pn_ahead = (PartitionNode*) linitial(queue); |
| int nChildren = list_length(pn_ahead->rules) + (pn_ahead->default_part ? 1 : 0); |
| HTAB* conHash = createConstraintHashTable(nChildren); |
| |
| /* get the list of part keys for this level */ |
| List *lpartkey = NIL; |
| for (int i = 0; i < pn_ahead->part->parnatts; i++) |
| { |
| lpartkey = lappend_int(lpartkey, pn_ahead->part->paratts[i]); |
| } |
| |
| /* now iterate over all partition nodes on this level */ |
| bool fFirstNode = true; |
| while (size > 0 && result) |
| { |
| PartitionNode *pn = (PartitionNode*) linitial(queue); |
| List *lrules = get_partition_rules(pn); |
| int curr_nChildren = list_length(lrules); |
| |
| if (curr_nChildren != nChildren) |
| { |
| result = false; |
| break; |
| } |
| |
| /* loop over the children's constraints of this node */ |
| ListCell *lc = NULL; |
| foreach(lc, lrules) |
| { |
| PartitionRule *pr = (PartitionRule*) lfirst(lc); |
| Node *curr_con = getPartConstraintsContainsKeys(pr->parchildrelid, rootOid, lpartkey); |
| bool found = false; |
| |
| /* we populate the hash table with the constraints of the children of the |
| * first node in this level */ |
| if (fFirstNode) |
| { |
| /* add current constraint to hash table */ |
| void *con_entry = hash_search(conHash, &curr_con, HASH_ENTER, &found); |
| if (con_entry == NULL) |
| { |
| ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); |
| } |
| ((ConNodeEntry*) con_entry)->entry = curr_con; |
| } |
| |
| /* starting from the second node in this level, we probe the children's constraints */ |
| else |
| { |
| hash_search(conHash, &curr_con, HASH_FIND, &found); |
| if (!found) |
| { |
| result = false; |
| break; |
| } |
| } |
| |
| if (pr->children) |
| { |
| queue = lappend(queue, pr->children); |
| } |
| } |
| size--; |
| fFirstNode = false; |
| queue = list_delete_first(queue); |
| pfree(lrules); |
| } |
| |
| hash_destroy(conHash); |
| pfree(lpartkey); |
| |
| } |
| |
| MemoryContextSwitchTo(callerMemoryContext); |
| MemoryContextDelete(uniformityMemoryContext); |
| |
| return result; |
| } |
| |
| /* Return the pg_class Oids of the relations representing leaf parts of the |
| * PartitionNode tree headed by the argument PartitionNode. |
| * |
| * The caller should be responsible for freeing the list after |
| * using it. |
| */ |
| List * |
| all_leaf_partition_relids(PartitionNode *pn) |
| { |
| if (NULL == pn) |
| { |
| return NIL; |
| } |
| |
| ListCell *lc; |
| List *leaf_relids = NIL; |
| |
| foreach(lc, pn->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| if (NULL != rule->children) |
| { |
| leaf_relids = list_concat(leaf_relids, all_leaf_partition_relids(rule->children)); |
| } |
| else |
| { |
| leaf_relids = lappend_oid(leaf_relids, rule->parchildrelid); |
| } |
| |
| } |
| if (NULL != pn->default_part) |
| { |
| if (NULL != pn->default_part->children) |
| { |
| leaf_relids = list_concat(leaf_relids, |
| all_leaf_partition_relids(pn->default_part->children)); |
| } |
| else |
| { |
| leaf_relids = lappend_oid(leaf_relids, pn->default_part->parchildrelid); |
| } |
| } |
| return leaf_relids; |
| } |
| |
| /* |
| * Given an Oid of a partition rule, return all leaf-level table Oids that are |
| * descendants of the given rule. |
| * Input: |
| * ruleOid - the oid of an entry in pg_partition_rule |
| * Output: |
| * a list of Oids of all leaf-level partition tables under the given rule in |
| * the partitioning hierarchy. |
| */ |
| static List * |
| rel_get_leaf_relids_from_rule(Oid ruleOid) |
| { |
| if (!OidIsValid(ruleOid)) |
| { |
| return NIL; |
| } |
| |
| List* lChildrenOid = NIL; |
| int nChildren = caql_getcount(NULL, cql("SELECT * FROM pg_partition_rule " |
| " WHERE parparentrule = :1 ", |
| ObjectIdGetDatum(ruleOid))); |
| /* if ruleOid is not parent of any rule, we have reached the leaf level and |
| * we need to append parchildrelid of this entry to the output |
| */ |
| if (nChildren == 0) |
| { |
| HeapTuple tuple = caql_getfirst(NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(ruleOid))); |
| Form_pg_partition_rule rule_desc = (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| lChildrenOid = lcons_oid(rule_desc->parchildrelid, lChildrenOid); |
| } |
| |
| /* Otherwise we are still in mid-level, hence recursively call this function |
| * on children rules of the given rule. |
| */ |
| else |
| { |
| cqContext *pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE parparentrule = :1 ", |
| ObjectIdGetDatum(ruleOid))); |
| HeapTuple tup; |
| while (HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| lChildrenOid = list_concat(lChildrenOid, rel_get_leaf_relids_from_rule(HeapTupleGetOid(tup))); |
| } |
| caql_endscan(pcqCtx); |
| } |
| |
| return lChildrenOid; |
| } |
| |
| /* Given a partition table Oid (root or interior), return the Oids of all leaf-level |
| * children below it. Similar to all_leaf_partition_relids() but takes Oid as input. |
| */ |
| List * |
| rel_get_leaf_children_relids(Oid relid) |
| { |
| PartStatus ps = rel_part_status(relid); |
| List *leaf_relids = NIL; |
| Assert(PART_STATUS_INTERIOR == ps || PART_STATUS_ROOT == ps); |
| |
| if (PART_STATUS_ROOT == ps) |
| { |
| PartitionNode *pn = get_parts(relid, 0 /*level*/, 0 /*parent*/, false /*inctemplate*/, |
| CurrentMemoryContext, true /*includesubparts*/); |
| leaf_relids = all_leaf_partition_relids(pn); |
| pfree(pn); |
| } |
| else if (PART_STATUS_INTERIOR == ps) |
| { |
| HeapTuple tuple = caql_getfirst(NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| if (HeapTupleIsValid(tuple)) |
| { |
| leaf_relids = rel_get_leaf_relids_from_rule(HeapTupleGetOid(tuple)); |
| heap_freetuple(tuple); |
| } |
| } |
| else if (PART_STATUS_LEAF == ps) |
| { |
| leaf_relids = list_make1_oid(relid); |
| } |
| |
| return leaf_relids; |
| } |
| |
| /* Return the pg_class Oids of the relations representing interior parts of the |
| * PartitionNode tree headed by the argument PartitionNode. |
| * |
| * The caller is responsible for freeing the list after using it. |
| */ |
| List * |
| all_interior_partition_relids(PartitionNode *pn) |
| { |
| if (NULL == pn) |
| { |
| return NIL; |
| } |
| |
| ListCell *lc; |
| List *interior_relids = NIL; |
| |
| foreach(lc, pn->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| if (rule->children) |
| { |
| interior_relids = lappend_oid(interior_relids, rule->parchildrelid); |
| interior_relids = list_concat(interior_relids, all_interior_partition_relids(rule->children)); |
| } |
| } |
| |
| if (pn->default_part) |
| { |
| if (pn->default_part->children) |
| { |
| interior_relids = lappend_oid(interior_relids, pn->default_part->parchildrelid); |
| interior_relids = list_concat(interior_relids, |
| all_interior_partition_relids(pn->default_part->children)); |
| } |
| } |
| |
| return interior_relids; |
| } |
| |
| /* |
| * Return the number of leaf parts of the partitioned table with the given oid |
| */ |
| int |
| countLeafPartTables(Oid rootOid) |
| { |
| Assert (rel_is_partitioned(rootOid)); |
| |
| PartitionNode *pn = get_parts(rootOid, 0 /* level */, 0 /* parent */, false /* inctemplate */, |
| CurrentMemoryContext, true /* include subparts */); |
| |
| List *lRelOids = all_leaf_partition_relids(pn); |
| Assert (list_length(lRelOids) > 0); |
| int count = list_length(lRelOids); |
| list_free(lRelOids); |
| pfree(pn); |
| return count; |
| } |
| |
| /* Return the pg_class Oids of the relations representing the parts |
| * of the PartitionRule tree headed by the argument PartitionRule. |
| * |
| * This local function is similar to all_partition_relids but enters |
| * at a PartitionRule which is more convenient in some cases, e.g., |
| * on the topRule of a PgPartRule. |
| */ |
| List * |
| all_prule_relids(PartitionRule *prule) |
| { |
| ListCell *lcr; |
| PartitionNode *pnode = NULL; |
| |
| List *oids = NIL; /* of pg_class Oid */ |
| |
| if ( prule ) |
| { |
| oids = lappend_oid(oids, prule->parchildrelid); |
| |
| pnode = prule->children; |
| if ( pnode ) |
| { |
| oids = list_concat(oids, all_prule_relids(pnode->default_part)); |
| foreach (lcr, pnode->rules) |
| { |
| PartitionRule *child = (PartitionRule*)lfirst(lcr); |
| oids = list_concat(oids, all_prule_relids(child)); |
| } |
| } |
| } |
| return oids; |
| } |
| |
| /* |
| * Returns the parent Oid from the given part Oid. |
| */ |
| Oid |
| rel_partition_get_root(Oid relid) |
| { |
| int fetchCount = 0; |
| |
| Oid masteroid = caql_getoid_plus(NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT inhparent FROM pg_inherits " |
| " WHERE inhrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!OidIsValid(masteroid)) |
| { |
| return InvalidOid; |
| } |
| |
| return masteroid; |
| } |
| |
| /* Get the top relation of the partitioned table of which the given |
| * relation is a part, or error. |
| * |
| * select parrelid |
| * from pg_partition |
| * where oid = ( |
| * select paroid |
| * from pg_partition_rule |
| * where parchildrelid = relid); |
| */ |
| Oid |
| rel_partition_get_master(Oid relid) |
| { |
| Oid paroid = InvalidOid; |
| Oid masteroid = InvalidOid; |
| int fetchCount = 0; |
| |
| paroid = caql_getoid(NULL, |
| cql("SELECT paroid FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| if (!OidIsValid(paroid)) |
| return InvalidOid; |
| |
| masteroid = caql_getoid_plus(NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT parrelid FROM pg_partition " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(paroid))); |
| |
| if (!fetchCount) |
| elog(ERROR, "could not find pg_partition entry with oid %d for " |
| "pg_partition_rule with child table %d", paroid, relid); |
| |
| |
| return masteroid; |
| |
| } /* end rel_partition_get_master */ |
| |
| /* given a relid, build a path list from the master tablename down to |
| * the partition for that relation, using partition names if possible, |
| * else rank or value expressions. |
| */ |
| List *rel_get_part_path1(Oid relid) |
| { |
| HeapTuple tuple; |
| Oid paroid = InvalidOid; |
| Oid parparentrule = InvalidOid; |
| List *lrelid = NIL; |
| |
| /* use the relid of the table to find the first rule */ |
| |
| tuple = caql_getfirst(NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| if (HeapTupleIsValid(tuple)) |
| { |
| Form_pg_partition_rule rule_desc = |
| (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| paroid = rule_desc->paroid; |
| parparentrule = rule_desc->parparentrule; |
| |
| /* prepend relid of child table to list */ |
| lrelid = lcons_oid(rule_desc->parchildrelid, lrelid); |
| |
| } |
| |
| if (!OidIsValid(paroid)) |
| return NIL; |
| |
| /* walk up the tree using the parent rule oid */ |
| |
| while (OidIsValid(parparentrule)) |
| { |
| tuple = caql_getfirst(NULL, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(parparentrule))); |
| if (HeapTupleIsValid(tuple)) |
| { |
| Form_pg_partition_rule rule_desc = |
| (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| paroid = rule_desc->paroid; |
| parparentrule = rule_desc->parparentrule; |
| |
| /* prepend relid of child table to list */ |
| lrelid = lcons_oid(rule_desc->parchildrelid, lrelid); |
| } |
| else |
| parparentrule = InvalidOid; /* we are done */ |
| } |
| |
| return lrelid; |
| |
| } /* end rel_get_part_path1 */ |
| |
| static List *rel_get_part_path(Oid relid) |
| { |
| PartitionNode *pNode = NULL; |
| Partition *part = NULL; |
| MemoryContext mcxt = CurrentMemoryContext; |
| List *lrelid = NIL; |
| List *lnamerank = NIL; |
| List *lnrv = NIL; |
| ListCell *lc, *lc2; |
| Oid masteroid = InvalidOid; |
| |
| masteroid = rel_partition_get_master(relid); |
| |
| if (!OidIsValid(masteroid)) |
| return NIL; |
| |
| /* call the guts of RelationBuildPartitionDesc */ |
| pNode = get_parts(masteroid, 0, 0, false, mcxt, true /*includesubparts*/); |
| |
| if (!pNode) |
| { |
| return NIL; |
| } |
| |
| part = pNode->part; |
| |
| /* get the relids for each table that corresponds to the partition |
| * heirarchy from the master to the specified partition |
| */ |
| lrelid = rel_get_part_path1(relid); |
| |
| /* walk the partition tree, finding the partition for each relid, |
| * and extract useful information (name, rank, value) |
| */ |
| foreach(lc, lrelid) |
| { |
| Oid parrelid = lfirst_oid(lc); |
| PartitionRule *prule; |
| int rulerank = 1; |
| |
| Assert(pNode); |
| |
| part = pNode->part; |
| |
| rulerank = 1; |
| |
| foreach (lc2, pNode->rules) |
| { |
| prule = (PartitionRule *)lfirst(lc2); |
| |
| if (parrelid == prule->parchildrelid) |
| { |
| pNode = prule->children; |
| goto L_rel_get_part_path_match; |
| } |
| rulerank++; |
| } |
| |
| /* if we get here, then must match the default partition */ |
| prule = pNode->default_part; |
| |
| Assert(parrelid == prule->parchildrelid); |
| |
| /* default partition must have a name (and no rank) */ |
| Assert (prule->parname && strlen(prule->parname)); |
| |
| pNode = prule->children; |
| rulerank = 0; |
| |
| L_rel_get_part_path_match: |
| |
| if (!rulerank) /* must be default, so it has a name, but no |
| * rank or value */ |
| { |
| lnrv = list_make3(prule->parname, NULL, NULL); |
| } |
| else if (part->parkind == 'l') /* list partition by value */ |
| { |
| char *idval = NULL; |
| ListCell *lc3; |
| List *l1 = (List *)prule->parlistvalues; |
| StringInfoData sid1; |
| int2 nkeys = part->parnatts; |
| int2 parcol = 0; |
| |
| initStringInfo(&sid1); |
| |
| /* foreach(lc3, l1) */ |
| /* don't loop -- just need first set of values */ |
| |
| lc3 = list_head(l1); |
| |
| if (lc3) |
| { |
| List *vals = lfirst(lc3); |
| ListCell *lcv = list_head(vals); |
| |
| /* Note: similar code in |
| * ruleutils.c:partition_rule_def_worker |
| */ |
| |
| for (parcol = 0; parcol < nkeys; parcol++) |
| { |
| Const *con = lfirst(lcv); |
| |
| if (lcv != list_head(vals)) |
| appendStringInfoString(&sid1, ", "); |
| |
| idval = |
| deparse_expression((Node*)con, |
| deparse_context_for(get_rel_name(relid), |
| relid), |
| false, false); |
| |
| appendStringInfo(&sid1, "%s", idval); |
| |
| lcv = lnext(lcv); |
| } /* end for parcol */ |
| } |
| /* list - no rank */ |
| lnrv = list_make3(prule->parname, NULL, sid1.data); |
| } |
| else /* range (or hash) - use rank (though rank is not really |
| * appropriate for hash) |
| */ |
| { |
| char *rtxt = palloc(NAMEDATALEN); |
| |
| sprintf(rtxt, "%d", rulerank); |
| |
| /* not list - rank, but no value */ |
| lnrv = list_make3(prule->parname, rtxt, NULL); |
| } |
| |
| /* build the list of (lists of name, rank, value) for each level */ |
| lnamerank = lappend(lnamerank, lnrv); |
| |
| } /* end foreach lc (walking list of relids) */ |
| |
| return lnamerank; |
| } /* end rel_get_part_path */ |
| |
| char * |
| rel_get_part_path_pretty(Oid relid, |
| char *separator, |
| char *lastsep) |
| { |
| List *lnamerank = NIL; |
| List *lnrv = NIL; |
| ListCell *lc, *lc2; |
| int maxlen; |
| StringInfoData sid1, sid2; |
| |
| lnamerank = rel_get_part_path(relid); |
| |
| maxlen = list_length(lnamerank); |
| |
| if (!maxlen) |
| return NULL; |
| |
| Assert(separator); |
| Assert(lastsep); |
| |
| initStringInfo(&sid1); |
| initStringInfo(&sid2); |
| |
| foreach(lc, lnamerank) |
| { |
| int lcnt = 0; |
| |
| lnrv = (List *)lfirst(lc); |
| |
| maxlen--; |
| |
| appendStringInfo(&sid1, "%s", maxlen ? separator : lastsep); |
| |
| lcnt = 0; |
| |
| foreach (lc2, lnrv) |
| { |
| char *str = (char *)lfirst(lc2); |
| |
| truncateStringInfo(&sid2, 0); |
| |
| switch(lcnt) |
| { |
| case 0: |
| if (str && strlen(str)) |
| { |
| appendStringInfo(&sid2, "\"%s\"", str); |
| goto l_pretty; |
| } |
| break; |
| |
| case 1: |
| if (str && strlen(str)) |
| { |
| appendStringInfo(&sid2, "FOR(RANK(%s))", str); |
| goto l_pretty; |
| } |
| break; |
| |
| case 2: |
| if (str && strlen(str)) |
| { |
| appendStringInfo(&sid2, "FOR(%s)", str); |
| goto l_pretty; |
| } |
| break; |
| default: |
| break; |
| } |
| lcnt++; |
| } |
| |
| l_pretty: |
| |
| appendStringInfo(&sid1, "%s", sid2.data); |
| } |
| |
| return sid1.data; |
| } /* end rel_get_part_path_pretty */ |
| |
| |
| /* |
| * ChoosePartitionName: given a table name, partition "depth", and a |
| * partition name, generate a unique name using ChooseRelationName. |
| * The partition depth is the raw parlevel (zero-based), which is |
| * incremented by 1 to be one-based. |
| * |
| * Note: calls CommandCounterIncrement. |
| * |
| * Returns a palloc'd string (from ChooseRelationName) |
| */ |
| char * |
| ChoosePartitionName(const char *tablename, int partDepth, |
| const char *partname, Oid namespaceId) |
| { |
| char *relname; |
| char depthstr[NAMEDATALEN]; |
| char prtstr[NAMEDATALEN]; |
| |
| /* build a relation name (see transformPartitionBy */ |
| snprintf(depthstr, sizeof(depthstr), "%d", partDepth+1); |
| snprintf(prtstr, sizeof(prtstr), "prt_%s", partname); |
| |
| relname = ChooseRelationName(tablename, |
| depthstr, /* depth */ |
| prtstr, /* part spec */ |
| namespaceId, |
| NULL); |
| CommandCounterIncrement(); |
| |
| return relname; |
| } |
| |
| /* |
| * Given a constant expression, build a datum according to |
| * part->paratts and the relation tupledesc. Needs work for |
| * type_coercion, multicol, etc. The returned Datum * is suitable for |
| * use in SelectPartition |
| */ |
| static Datum * |
| magic_expr_to_datum(Relation rel, PartitionNode *partnode, |
| Node *expr, bool **ppisnull) |
| { |
| Partition *part = partnode->part; |
| TupleDesc tupleDesc; |
| Datum *values; |
| bool *isnull; |
| int ii, jj; |
| |
| Assert(rel); |
| |
| tupleDesc = RelationGetDescr(rel); |
| |
| /* Preallocate values/isnull arrays */ |
| ii = tupleDesc->natts; |
| values = (Datum *) palloc(ii * sizeof(Datum)); |
| isnull = (bool *) palloc(ii * sizeof(bool)); |
| memset(values, 0, ii * sizeof(Datum)); |
| memset(isnull, true, ii * sizeof(bool)); |
| |
| *ppisnull = isnull; |
| |
| Assert (IsA(expr, List)); |
| |
| jj = list_length((List *)expr); |
| |
| if (jj > ii) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("too many columns in boundary specification (%d > %d)", |
| jj, ii))); |
| |
| if (jj > part->parnatts) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("too many columns in boundary specification (%d > %d)", |
| jj, part->parnatts))); |
| |
| { |
| ListCell *lc; |
| int i = 0; |
| |
| foreach(lc, (List *)expr) |
| { |
| Node *n1 = (Node *) lfirst(lc); |
| Const *c1; |
| AttrNumber attno = part->paratts[i++]; |
| Form_pg_attribute attribute = tupleDesc->attrs[attno - 1]; |
| Oid lhsid = attribute->atttypid; |
| |
| if (!IsA(n1, Const)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("Not a constant expression"))); |
| |
| c1 = (Const *)n1; |
| |
| if (lhsid != c1->consttype) |
| { |
| /* see coerce_partition_value */ |
| Node *out; |
| |
| out = coerce_partition_value(n1, lhsid, attribute->atttypmod, |
| char_to_parttype(partnode->part->parkind)); |
| if (!out) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("cannot coerce column type (%s versus %s)", |
| format_type_be(c1->consttype), |
| format_type_be(lhsid)))); |
| |
| Assert(IsA(out, Const)); |
| |
| c1 = (Const *)out; |
| } |
| |
| /* XXX: cache */ |
| values[attno - 1] = c1->constvalue; |
| isnull[attno - 1] = c1->constisnull; |
| } |
| } |
| |
| return values; |
| } /* end magic_expr_to_datum */ |
| |
| /* |
| * Assume the partition rules are in the "correct" order and return |
| * the nth rule (1-based). If rnk is negative start from the end, ie |
| * -1 is the last rule. |
| */ |
| static Oid |
| selectPartitionByRank(PartitionNode *partnode, int rnk) |
| { |
| Oid relid = InvalidOid; |
| List *rules = partnode->rules; |
| PartitionRule *rule; |
| |
| Assert(partnode->part->parkind == 'r'); |
| |
| if (rnk > list_length(rules)) |
| return relid; |
| |
| if (rnk == 0) |
| return relid; |
| |
| if (rnk > 0) |
| rnk--; /* list_nth is zero-based, not one-based */ |
| else if (rnk < 0) |
| { |
| rnk = list_length(rules) + rnk; /* if negative go from end */ |
| |
| /* mpp-3265 */ |
| if (rnk < 0) /* oops -- too negative */ |
| return relid; |
| } |
| |
| rule = (PartitionRule *)list_nth(rules, rnk); |
| |
| return rule->parchildrelid; |
| } /* end selectPartitionByRank */ |
| |
| static bool compare_partn_opfuncid(PartitionNode *partnode, |
| char *pub, char *compare_op, |
| List *colvals, |
| Datum *values, bool *isnull, |
| TupleDesc tupdesc) |
| |
| { |
| Partition *part = partnode->part; |
| List *last_opname = list_make2(makeString(pub), |
| makeString(compare_op)); |
| List *opname = NIL; |
| ListCell *lc; |
| int numCols = 0; |
| int colCnt = 0; |
| int ii = 0; |
| |
| if (1 == strlen(compare_op)) |
| { |
| /* handle case of less than or greater than */ |
| if (0 == strcmp("<", compare_op)) |
| compare_op = "<="; |
| if (0 == strcmp(">", compare_op)) |
| compare_op = ">="; |
| |
| /* for a list of values, when performing less than or greater |
| * than comparison, only the final value is compared using |
| * less than or greater. All prior values must be compared |
| * with LTE/GTE. For example, comparing the list (1,2,3) to |
| * see if it is less than (1,2,4), we see that 1 <= 1, 2 <= 2, |
| * and 3 < 4. So the last_opname is the specified compare_op, |
| * and the prior opnames are LTE or GTE. |
| */ |
| |
| } |
| |
| opname = list_make2(makeString(pub), makeString(compare_op)); |
| |
| colCnt = numCols = list_length(colvals); |
| |
| foreach(lc, colvals) |
| { |
| Const *c = lfirst(lc); |
| AttrNumber attno = part->paratts[ii]; |
| |
| if (isnull && isnull[attno - 1]) |
| { |
| if (!c->constisnull) |
| return false; |
| } |
| else |
| { |
| Oid lhsid = tupdesc->attrs[attno - 1]->atttypid; |
| Oid rhsid = lhsid; |
| Oid opfuncid; |
| Datum res; |
| Datum d = values[attno - 1]; |
| |
| if (1 == colCnt) |
| { |
| opname = last_opname; |
| } |
| |
| opfuncid = get_opfuncid_by_opname(opname, lhsid, rhsid); |
| res = OidFunctionCall2(opfuncid, c->constvalue, d); |
| |
| if (!DatumGetBool(res)) |
| return false; |
| } |
| |
| ii++; |
| colCnt--; |
| } /* end foreach */ |
| |
| return true; |
| } /* end compare_partn_opfuncid */ |
| |
| /* |
| * Given a partition-by-list PartitionNode, search for |
| * a part that matches the given datum value. |
| * |
| * Input parameters: |
| * partnode: the PartitionNode that we search for matched parts |
| * values, isnull: datum values to search for parts |
| * tupdesc: TupleDesc for retrieving values |
| * accessMethods: PartitionAccessMethods |
| * foundOid: output parameter for matched part Oid |
| * prule: output parameter for matched part PartitionRule |
| * exprTypeOid: type of the expression used to select partition |
| */ |
| static PartitionNode * |
| selectListPartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, Oid *foundOid, PartitionRule **prule, |
| Oid exprTypeOid) |
| { |
| ListCell *lc; |
| Partition *part = partnode->part; |
| MemoryContext oldcxt = NULL; |
| PartitionListState *ls; |
| |
| if (accessMethods && accessMethods->amstate[partnode->part->parlevel]) |
| ls = (PartitionListState *)accessMethods->amstate[partnode->part->parlevel]; |
| else |
| { |
| int natts = partnode->part->parnatts; |
| |
| ls = palloc(sizeof(PartitionListState)); |
| |
| ls->eqfuncs = palloc(sizeof(FmgrInfo) * natts); |
| ls->eqinit = palloc0(sizeof(bool) * natts); |
| |
| if (accessMethods) |
| accessMethods->amstate[partnode->part->parlevel] = (void *)ls; |
| } |
| |
| if (accessMethods && accessMethods->part_cxt) |
| oldcxt = MemoryContextSwitchTo(accessMethods->part_cxt); |
| |
| *foundOid = InvalidOid; |
| |
| /* With LIST, we have no choice at the moment except to be exhaustive */ |
| foreach(lc, partnode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| List *vals = rule->parlistvalues; |
| ListCell *lc2; |
| bool matched = false; |
| |
| /* |
| * list values are stored in a list of lists to support multi column |
| * partitions. |
| * |
| * At this level, we're processing the list of possible values for the |
| * given rule, for example: |
| * values(1, 2, 3) |
| * values((1, '2005-01-01'), (2, '2006-01-01')) |
| * |
| * Each iteraction is one element of the values list. In the first |
| * example, we iterate '1', '2' then '3'. For the second, we iterate |
| * through '(1, '2005-01-01')' then '(2, '2006-01-01')'. It's for that |
| * reason that we need the inner loop. |
| */ |
| foreach(lc2, vals) |
| { |
| ListCell *lc3; |
| List *colvals = (List *)lfirst(lc2); |
| int i = 0; |
| |
| matched = true; /* prove untrue */ |
| |
| foreach(lc3, colvals) |
| { |
| Const *c = lfirst(lc3); |
| AttrNumber attno = part->paratts[i]; |
| |
| if (isnull[attno - 1]) |
| { |
| if (!c->constisnull) |
| { |
| matched = false; |
| break; |
| } |
| } |
| else if (c->constisnull) |
| { |
| /* constant is null but datum isn't so break */ |
| matched = false; |
| break; |
| } |
| else |
| { |
| Datum res; |
| Datum d = values[attno - 1]; |
| FmgrInfo *finfo; |
| |
| if (!ls->eqinit[i]) |
| { |
| |
| /* |
| * Compute the type of the LHS and RHS for the equality comparator. |
| * The way we call the comparator is comp(expr, rule) |
| * So lhstypid = type(expr) and rhstypeid = type(rule) |
| */ |
| |
| /* The tupdesc tuple descriptor matches the table schema, so it has the rule type */ |
| Oid rhstypid = tupdesc->attrs[attno - 1]->atttypid; |
| |
| /* |
| * exprTypeOid is passed to us from our caller which evaluated the expression. |
| * In some cases (e.g legacy optimizer doing explicit casting), we don't compute |
| * specify exprTypeOid. |
| * Assume lhstypid = rhstypid in those cases |
| */ |
| Oid lhstypid = exprTypeOid; |
| if (!OidIsValid(lhstypid)) |
| { |
| lhstypid = rhstypid; |
| } |
| |
| List *opname = list_make2(makeString("pg_catalog"), |
| makeString("=")); |
| |
| Oid opfuncid = get_opfuncid_by_opname(opname, lhstypid, rhstypid); |
| fmgr_info(opfuncid, &(ls->eqfuncs[i])); |
| ls->eqinit[i] = true; |
| } |
| |
| finfo = &(ls->eqfuncs[i]); |
| res = FunctionCall2(finfo, d, c->constvalue); |
| |
| if (!DatumGetBool(res)) |
| { |
| /* found it */ |
| matched = false; |
| break; |
| } |
| } |
| i++; |
| } |
| if (matched) |
| break; |
| } |
| |
| if (matched) |
| { |
| *foundOid = rule->parchildrelid; |
| *prule = rule; |
| |
| /* go to the next level */ |
| if (oldcxt) |
| MemoryContextSwitchTo(oldcxt); |
| return rule->children; |
| } |
| } |
| |
| if (oldcxt) |
| MemoryContextSwitchTo(oldcxt); |
| |
| return NULL; |
| |
| } |
| |
| /* |
| * get_less_than_oper() |
| * |
| * Retrieves the appropriate comparator that knows how to handle |
| * the two types lhsid, rhsid. |
| * |
| * Input parameters: |
| * lhstypid: Type oid of the LHS of the comparator |
| * rhstypid: Type oid of the RHS of the comparator |
| * strictlyless: If true, requests the 'strictly less than' operator instead of 'less or equal than' |
| * |
| * Returns: The Oid of the appropriate comparator; throws an ERROR if no such comparator exists |
| * |
| */ |
| static Oid |
| get_less_than_oper(Oid lhstypid, Oid rhstypid, bool strictlyless) |
| { |
| Value *str = strictlyless ? makeString("<") : makeString("<="); |
| Value *pub = makeString("pg_catalog"); |
| List *opname = list_make2(pub, str); |
| |
| Oid funcid = get_opfuncid_by_opname(opname, lhstypid, rhstypid); |
| list_free_deep(opname); |
| |
| return funcid; |
| } |
| |
| /* |
| * get_comparator |
| * Retrieves the comparator function between the expression and the partition rules |
| * |
| * Input: |
| * keyno: Index in the key array of the partitioning key considered |
| * rs: the current PartitionRangeState |
| * ruleTypeOid: Oid for the type of the partition rules |
| * exprTypeOid: Oid for the type of the expressions |
| * strictlyless: If true, the operator for strictly less than (LT) is retrieved. Otherwise, |
| * it's less or equal than (LE) |
| * is_direct: If true, then the "direct" comparison (expression OP rule) is retrieved. |
| * Otherwise, it's the "inverse" comparison (rule OP expression) |
| * |
| */ |
| static FmgrInfo * |
| get_less_than_comparator(int keyno, PartitionRangeState *rs, Oid ruleTypeOid, Oid exprTypeOid, bool strictlyless, bool is_direct) |
| { |
| |
| Assert(NULL != rs); |
| |
| Oid lhsOid = InvalidOid; |
| Oid rhsOid = InvalidOid; |
| FmgrInfo *funcInfo = NULL; |
| |
| if (is_direct && strictlyless) { |
| /* Looking for expr < partRule comparator */ |
| funcInfo = &rs->ltfuncs_direct[keyno]; |
| } else if (is_direct && !strictlyless) { |
| /* Looking for expr <= partRule comparator */ |
| funcInfo = &rs->lefuncs_direct[keyno]; |
| } else if (!is_direct && strictlyless) { |
| /* Looking for partRule < expr comparator */ |
| funcInfo = &rs->ltfuncs_inverse[keyno]; |
| } else if (!is_direct && !strictlyless) { |
| /* Looking for partRule <= expr comparator */ |
| funcInfo = &rs->lefuncs_inverse[keyno]; |
| } |
| |
| Assert(NULL != funcInfo); |
| |
| if (!OidIsValid(funcInfo->fn_oid)) { |
| /* We haven't looked up this comparator before, let's do it now */ |
| |
| if (is_direct) { |
| /* Looking for "direct" comparators (expr OP partRule ) */ |
| lhsOid = exprTypeOid; |
| rhsOid = ruleTypeOid; |
| } |
| else { |
| /* Looking for "inverse" comparators (partRule OP expr ) */ |
| lhsOid = ruleTypeOid; |
| rhsOid = exprTypeOid; |
| } |
| |
| Oid funcid = get_less_than_oper(lhsOid, rhsOid, strictlyless); |
| fmgr_info(funcid, funcInfo); |
| } |
| |
| Assert(OidIsValid(funcInfo->fn_oid)); |
| return funcInfo; |
| } |
| |
| /* |
| * range_test |
| * Test if an expression value falls in the range of a given partition rule |
| * |
| * Input parameters: |
| * tupval: The value of the expression |
| * ruleTypeOid: The type of the partition rule boundaries |
| * exprTypeOid: The type of the expression (can be different from ruleTypeOid |
| * if types can be directly compared with each other) |
| * rs: The partition range state |
| * keyno: The index of the partitioning key considered (for composite partitioning keys) |
| * rule: The rule whose boundaries we're testing |
| * |
| */ |
| static int |
| range_test(Datum tupval, Oid ruleTypeOid, Oid exprTypeOid, PartitionRangeState *rs, int keyno, |
| PartitionRule *rule) |
| { |
| Const *c = NULL; |
| FmgrInfo *finfo; |
| Datum res; |
| |
| Assert(PointerIsValid(rule->parrangestart) || |
| PointerIsValid(rule->parrangeend)); |
| |
| /* might be a rule with no START value */ |
| if (PointerIsValid(rule->parrangestart)) |
| { |
| Assert(IsA(rule->parrangestart, List)); |
| #if NOT_YET |
| c = (Const *)list_nth((List *)rule->parrangestart, keyno); |
| #else |
| c = (Const *)linitial((List *)rule->parrangestart); |
| #endif |
| |
| /* |
| * Is the value in the range? |
| * If rule->parrangestartincl, we request for comparator ruleVal <= exprVal ( ==> strictly_less = false) |
| * Otherwise, we request comparator ruleVal < exprVal ( ==> strictly_less = true) |
| */ |
| finfo = get_less_than_comparator(keyno, rs, ruleTypeOid, exprTypeOid, !rule->parrangestartincl /* strictly_less */, false /* is_direct */); |
| res = FunctionCall2(finfo, c->constvalue, tupval); |
| |
| if (!DatumGetBool(res)) |
| return -1; |
| } |
| |
| /* There might be no END value */ |
| if (PointerIsValid(rule->parrangeend)) |
| { |
| #if NOT_YET |
| c = (Const *)list_nth((List *)rule->parrangeend, keyno); |
| #else |
| c = (Const *)linitial((List *)rule->parrangeend); |
| #endif |
| |
| /* |
| * Is the value in the range? |
| * If rule->parrangeendincl, we request for comparator exprVal <= ruleVal ( ==> strictly_less = false) |
| * Otherwise, we request comparator exprVal < ruleVal ( ==> strictly_less = true) |
| */ |
| finfo = get_less_than_comparator(keyno, rs, ruleTypeOid, exprTypeOid, !rule->parrangeendincl /* strictly_less */, true /* is_direct */); |
| res = FunctionCall2(finfo, tupval, c->constvalue); |
| |
| if (!DatumGetBool(res)) |
| { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * Given a partition specific part, a tuple as represented by values and isnull and |
| * a list of rules, return an Oid in *foundOid or the next set of rules. |
| */ |
| static PartitionNode * |
| selectRangePartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| Oid *foundOid, int *pSearch, PartitionRule **prule, Oid exprTypeOid) |
| { |
| List *rules = partnode->rules; |
| int high = list_length(rules) - 1; |
| int low = 0; |
| int searchpoint = 0; |
| int mid = 0; |
| bool matched = false; |
| PartitionRule *rule = NULL; |
| PartitionNode *pNode = NULL; |
| PartitionRangeState *rs = NULL; |
| MemoryContext oldcxt = NULL; |
| |
| Assert(partnode->part->parkind == 'r'); |
| /* For composite partitioning keys, exprTypeOid should always be InvalidOid */ |
| AssertImply(partnode->part->parnatts > 1, !OidIsValid(exprTypeOid)); |
| |
| if (accessMethods && accessMethods->amstate[partnode->part->parlevel]) |
| rs = (PartitionRangeState *)accessMethods->amstate[partnode->part->parlevel]; |
| else |
| { |
| int natts = partnode->part->parnatts; |
| |
| /* |
| * We're still in our caller's memory context so |
| * the memory will persist long enough for us. |
| */ |
| rs = palloc(sizeof(PartitionRangeState)); |
| rs->lefuncs_direct = palloc0(sizeof(FmgrInfo) * natts); |
| rs->ltfuncs_direct = palloc0(sizeof(FmgrInfo) * natts); |
| rs->lefuncs_inverse = palloc0(sizeof(FmgrInfo) * natts); |
| rs->ltfuncs_inverse = palloc0(sizeof(FmgrInfo) * natts); |
| |
| /* |
| * Set the function Oid to InvalidOid to signal that we |
| * haven't looked up this function yet |
| */ |
| for (int keyno = 0; keyno < natts; keyno++) |
| { |
| rs->lefuncs_direct[keyno].fn_oid = InvalidOid; |
| rs->ltfuncs_direct[keyno].fn_oid = InvalidOid; |
| rs->lefuncs_inverse[keyno].fn_oid = InvalidOid; |
| rs->lefuncs_inverse[keyno].fn_oid = InvalidOid; |
| } |
| |
| /* |
| * Unrolling the rules into an array currently works for the |
| * top level partition only |
| */ |
| if (partnode->part->parlevel == 0) |
| { |
| int i = 0; |
| ListCell *lc; |
| |
| rs->rules = palloc(sizeof(PartitionRule *) * list_length(rules)); |
| |
| foreach(lc, rules) |
| rs->rules[i++] = (PartitionRule *)lfirst(lc); |
| } |
| else |
| rs->rules = NULL; |
| } |
| |
| if (accessMethods && accessMethods->part_cxt) |
| oldcxt = MemoryContextSwitchTo(accessMethods->part_cxt); |
| |
| *foundOid = InvalidOid; |
| |
| /* |
| * Use a binary search to try and pin point the region within the set of |
| * rules where the rule is. If the partition is across a single column, |
| * the rule located by the binary search is the only possible candidate. |
| * If the partition is across more than one column, we need to search |
| * sequentially to either side of the rule to see if the match is there. |
| * The reason for this complexity is the nature of find a point within an |
| * interval. |
| * |
| * Consider the following intervals: |
| * |
| * 1. start( 1, 8) end( 10, 9) |
| * 2. start( 1, 9) end( 15, 10) |
| * 3. start( 1, 11) end(100, 12) |
| * 4. start(15, 10) end( 30, 11) |
| * |
| * If we were to try and find the partition for a tuple (25, 10), using the |
| * binary search for the first element, we'd select partition 3 but |
| * partition 4 is also a candidate. It is only when we look at the second |
| * element that we find the single definitive rule. |
| */ |
| while (low <= high) |
| { |
| AttrNumber attno = partnode->part->paratts[0]; |
| Datum exprValue = values[attno - 1]; |
| int ret; |
| |
| mid = low + (high - low)/2; |
| |
| if (rs->rules) |
| rule = rs->rules[mid]; |
| else |
| rule = (PartitionRule *)list_nth(rules, mid); |
| |
| if (isnull[attno - 1]) |
| { |
| pNode = NULL; |
| goto l_fin_range; |
| } |
| |
| Oid ruleTypeOid = tupdesc->attrs[attno - 1]->atttypid; |
| if (OidIsValid(exprTypeOid)) |
| { |
| ret = range_test(exprValue, ruleTypeOid, exprTypeOid, rs, 0, rule); |
| } |
| else |
| { |
| /* |
| * In some cases, we don't have an expression type oid. In those cases, the expression and |
| * partition rules have the same type. |
| */ |
| ret = range_test(exprValue, ruleTypeOid, ruleTypeOid, rs, 0, rule); |
| } |
| |
| if (ret > 0) |
| { |
| searchpoint = mid; |
| low = mid + 1; |
| continue; |
| } |
| else if (ret < 0) |
| { |
| high = mid - 1; |
| continue; |
| } |
| else |
| { |
| matched = true; |
| break; |
| } |
| } |
| |
| if (matched) |
| { |
| int j; |
| |
| /* Non-composite partition key, we matched so we're done */ |
| if (partnode->part->parnatts == 1) |
| { |
| *foundOid = rule->parchildrelid; |
| *prule = rule; |
| |
| pNode = rule->children; |
| goto l_fin_range; |
| } |
| |
| /* We have more than one partition key.. Must match on the other keys as well */ |
| j = mid; |
| do |
| { |
| int i; |
| bool matched = true; |
| bool first_fail = false; |
| |
| for (i = 0; i < partnode->part->parnatts; i++) |
| { |
| AttrNumber attno = partnode->part->paratts[i]; |
| Datum d = values[attno - 1]; |
| int ret; |
| |
| if (j != mid) |
| rule = (PartitionRule *)list_nth(rules, j); |
| |
| if (isnull[attno - 1]) |
| { |
| pNode = NULL; |
| goto l_fin_range; |
| } |
| |
| Oid ruleTypeOid = tupdesc->attrs[attno - 1]->atttypid; |
| /* For composite partition keys, we don't support casting comparators, so both sides must be of identical types */ |
| Assert(!OidIsValid(exprTypeOid)); |
| ret = range_test(d, ruleTypeOid, ruleTypeOid, |
| rs, i, rule); |
| if (ret != 0) |
| { |
| matched = false; |
| |
| /* |
| * If we've gone beyond the boundary of rules which match |
| * the first tuple, no use looking further |
| */ |
| if (i == 0) |
| first_fail = true; |
| |
| break; |
| } |
| } |
| |
| if (first_fail) |
| break; |
| |
| if (matched) |
| { |
| *foundOid = rule->parchildrelid; |
| *prule = rule; |
| |
| pNode = rule->children; |
| goto l_fin_range; |
| } |
| |
| } |
| while (--j >= 0); |
| |
| j = mid; |
| do |
| { |
| int i; |
| bool matched = true; |
| bool first_fail = false; |
| |
| for (i = 0; i < partnode->part->parnatts; i++) |
| { |
| AttrNumber attno = partnode->part->paratts[i]; |
| Datum d = values[attno - 1]; |
| int ret; |
| |
| rule = (PartitionRule *)list_nth(rules, j); |
| |
| if (isnull[attno - 1]) |
| { |
| pNode = NULL; |
| goto l_fin_range; |
| } |
| |
| Oid ruleTypeOid = tupdesc->attrs[attno - 1]->atttypid; |
| /* For composite partition keys, we don't support casting comparators, so both sides must be of identical types */ |
| Assert(!OidIsValid(exprTypeOid)); |
| ret = range_test(d, ruleTypeOid, ruleTypeOid, rs, i, rule); |
| if (ret != 0) |
| { |
| matched = false; |
| |
| /* |
| * If we've gone beyond the boundary of rules which match |
| * the first tuple, no use looking further |
| */ |
| if (i == 0) |
| first_fail = true; |
| |
| break; |
| } |
| } |
| |
| if (first_fail) |
| break; |
| if (matched) |
| { |
| *foundOid = rule->parchildrelid; |
| *prule = rule; |
| |
| pNode = rule->children; |
| goto l_fin_range; |
| } |
| |
| } |
| while (++j < list_length(rules)); |
| } /* end if matched */ |
| |
| pNode = NULL; |
| |
| l_fin_range: |
| if (pSearch) |
| *pSearch = searchpoint; |
| |
| if (oldcxt) |
| MemoryContextSwitchTo(oldcxt); |
| |
| if (accessMethods) |
| accessMethods->amstate[partnode->part->parlevel] = (void *)rs; |
| |
| return pNode; |
| } /* end selectrangepartition */ |
| |
| |
| /* select partition via hash */ |
| static PartitionNode * |
| selectHashPartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, Oid *found, PartitionRule **prule) |
| { |
| uint32 hash = 0; |
| int i; |
| int part; |
| PartitionRule *rule; |
| MemoryContext oldcxt = NULL; |
| |
| if (accessMethods && accessMethods->part_cxt) |
| oldcxt = MemoryContextSwitchTo(accessMethods->part_cxt); |
| |
| for (i = 0; i < partnode->part->parnatts; i++) |
| { |
| AttrNumber attnum = partnode->part->paratts[i]; |
| |
| /* rotate hash left 1 bit at each step */ |
| hash = (hash << 1) | ((hash & 0x80000000) ? 1 : 0); |
| |
| /* |
| * If we found a NULL, just pretend it has a hashcode of 0 (like the |
| * hash join code does. |
| */ |
| if (isnull[attnum - 1]) |
| continue; |
| else |
| { |
| Oid opclass = partnode->part->parclass[i]; |
| Oid hashfunc = get_opclass_proc(opclass, 0, HASHPROC); |
| Datum d = values[attnum - 1]; |
| hash ^= DatumGetUInt32(OidFunctionCall1(hashfunc, d)); |
| } |
| } |
| |
| part = hash % list_length(partnode->rules); |
| |
| rule = (PartitionRule *)list_nth(partnode->rules, part); |
| |
| *found = rule->parchildrelid; |
| *prule = rule; |
| |
| if (oldcxt) |
| MemoryContextSwitchTo(oldcxt); |
| |
| return rule->children; |
| } |
| |
| /* |
| * selectPartition1() |
| * |
| * Given pdata and prules, try and find a suitable partition for the input key. |
| * values is an array of datums representing the partitioning key, isnull |
| * tells us which of those is NULL. pSearch allows the caller to get the |
| * position in the partition range where the key falls (might be hypothetical). |
| */ |
| static Oid |
| selectPartition1(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| int *pSearch, |
| PartitionNode **ppn_out) |
| { |
| Oid relid = InvalidOid; |
| Partition *part = partnode->part; |
| PartitionNode *pn = NULL; |
| PartitionRule *prule = NULL; |
| |
| if (ppn_out) |
| *ppn_out = NULL; |
| |
| /* what kind of partition? */ |
| switch (part->parkind) |
| { |
| case 'r': /* range */ |
| pn = selectRangePartition(partnode, values, isnull, tupdesc, |
| accessMethods, &relid, pSearch, &prule, InvalidOid); |
| break; |
| case 'h': /* hash */ |
| pn = selectHashPartition(partnode, values, isnull, tupdesc, |
| accessMethods, &relid, &prule); |
| break; |
| case 'l': /* list */ |
| pn = selectListPartition(partnode, values, isnull, tupdesc, |
| accessMethods, &relid, &prule, InvalidOid); |
| break; |
| default: |
| elog(ERROR, "unrecognized partitioning kind '%c'", |
| part->parkind); |
| break; |
| } |
| |
| if (pn) |
| { |
| if (ppn_out) |
| { |
| *ppn_out = pn; |
| return relid; |
| } |
| return selectPartition1(pn, values, isnull, tupdesc, accessMethods, |
| pSearch, ppn_out); |
| } |
| else |
| { |
| /* retry a default */ |
| if (partnode->default_part && !OidIsValid(relid)) |
| { |
| if (partnode->default_part->children) |
| { |
| if (ppn_out) |
| { |
| *ppn_out = partnode->default_part->children; |
| |
| /* don't return the relid, it is invalid -- return |
| * the relid of the default partition instead |
| */ |
| return partnode->default_part->parchildrelid; |
| } |
| return selectPartition1(partnode->default_part->children, |
| values, isnull, tupdesc, accessMethods, |
| pSearch, ppn_out); |
| } |
| else |
| return partnode->default_part->parchildrelid; |
| } |
| else |
| return relid; |
| } |
| } |
| |
| Oid |
| selectPartition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods) |
| { |
| return selectPartition1(partnode, values, isnull, tupdesc, accessMethods, |
| NULL, NULL); |
| } |
| |
| /* |
| * get_next_level_matched_partition() |
| * |
| * Given pdata and prules, try to find a suitable partition for the input key. |
| * values is an array of datums representing the partitioning key, isnull |
| * tells us which of those is NULL. It will return NULL if no part matches. |
| * |
| * Input parameters: |
| * partnode: the PartitionNode that we search for matched parts |
| * values, isnull: datum values to search for parts |
| * tupdesc: TupleDesc for retrieving values |
| * accessMethods: PartitionAccessMethods |
| * exprTypid: the type of the datum |
| * |
| * return: PartitionRule of which constraints match the input key |
| */ |
| PartitionRule* |
| get_next_level_matched_partition(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods, |
| Oid exprTypid) |
| { |
| Oid relid = InvalidOid; |
| Partition *part = partnode->part; |
| PartitionRule *prule = NULL; |
| |
| /* what kind of partition? */ |
| switch (part->parkind) |
| { |
| case 'r': /* range */ |
| selectRangePartition(partnode, values, isnull, tupdesc, |
| accessMethods, &relid, NULL, &prule, exprTypid); |
| break; |
| case 'l': /* list */ |
| selectListPartition(partnode, values, isnull, tupdesc, |
| accessMethods, &relid, &prule, exprTypid); |
| break; |
| default: |
| elog(ERROR, "unrecognized partitioning kind '%c'", |
| part->parkind); |
| break; |
| } |
| |
| if (NULL != prule) |
| { |
| return prule; |
| } |
| /* retry a default */ |
| return partnode->default_part; |
| } |
| |
| /* |
| * get_part_rule |
| * |
| * find PartitionNode and the PartitionRule for a partition if it |
| * exists. |
| * |
| * If bExistError is not set, just return the PgPartRule. |
| * If bExistError is set, return an error message based upon |
| * bMustExist. That is, if the table does not exist and |
| * bMustExist=true then return an error. Conversely, if the table |
| * *does* exist and bMustExist=false then return an error. |
| * |
| * If pSearch is set, return a ptr to the position where the pid |
| * *might* be. For get_part_rule1, pNode is the position to start at |
| * (ie not necessarily at the top). relname is a char string to |
| * describe the current relation/partition for error messages, eg |
| * 'relation "foo"' or 'partition "baz" of relation "foo"'. |
| * |
| */ |
| PgPartRule* |
| get_part_rule1(Relation rel, |
| AlterPartitionId *pid, |
| bool bExistError, |
| bool bMustExist, |
| MemoryContext mcxt, |
| int *pSearch, |
| PartitionNode *pNode, |
| char *relname, |
| PartitionNode **ppNode |
| ) |
| { |
| char namBuf[NAMEDATALEN]; /* the real partition name */ |
| |
| /* a textual representation of the partition id (for error msgs) */ |
| char partIdStr[(NAMEDATALEN * 2)]; |
| |
| PgPartRule *prule = NULL; |
| |
| Oid partrelid = InvalidOid; |
| int idrank = 0; /* only set for range partns by rank */ |
| |
| if (!pid) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("No partition id specified for %s", |
| relname), |
| errOmitLocation(true))); |
| |
| namBuf[0] = 0; |
| |
| /* build the partition "id string" for error messages as a |
| * partition name, value, or rank. |
| * |
| * Later on, if we discover |
| * (the partition exists) and |
| * (it has a name) |
| * then we update the partIdStr to the name |
| */ |
| switch (pid->idtype) |
| { |
| case AT_AP_IDNone: /* no ID */ |
| /* should never happen */ |
| partIdStr[0] = 0; |
| |
| break; |
| case AT_AP_IDName: /* IDentify by Name */ |
| snprintf(partIdStr, sizeof(partIdStr), " \"%s\"", |
| strVal(pid->partiddef)); |
| snprintf(namBuf, sizeof(namBuf), "%s", |
| strVal(pid->partiddef)); |
| break; |
| case AT_AP_IDValue: /* IDentifier FOR Value */ |
| snprintf(partIdStr, sizeof(partIdStr), " for specified value"); |
| break; |
| case AT_AP_IDRank: /* IDentifier FOR Rank */ |
| { |
| snprintf(partIdStr, sizeof(partIdStr), " for specified rank"); |
| |
| #ifdef WIN32 |
| #define round(x) (x+0.5) |
| #endif |
| if (IsA(pid->partiddef, Integer)) |
| idrank = intVal(pid->partiddef); |
| else if (IsA(pid->partiddef, Float)) |
| idrank = floor(floatVal(pid->partiddef)); |
| else |
| Assert(false); |
| |
| snprintf(partIdStr, sizeof(partIdStr), |
| " for rank %d", |
| idrank); |
| } |
| break; |
| case AT_AP_ID_oid: /* IDentifier by oid */ |
| snprintf(partIdStr, sizeof(partIdStr), " for oid %u", |
| *((Oid *)(pid->partiddef))); |
| break; |
| case AT_AP_IDDefault: /* IDentify DEFAULT partition */ |
| snprintf(partIdStr, sizeof(partIdStr), " for DEFAULT"); |
| break; |
| case AT_AP_IDRule: |
| { |
| PgPartRule *p = linitial((List *)pid->partiddef); |
| snprintf(partIdStr, sizeof(partIdStr), "%s", |
| p->partIdStr); |
| return p; |
| break; |
| } |
| default: /* XXX XXX */ |
| Assert(false); |
| |
| } |
| |
| if (bExistError && !pNode) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("%s is not partitioned", |
| relname), |
| errOmitLocation(true))); |
| |
| /* if id is a value or rank, get the relid of the partition if |
| * it exists */ |
| if (pNode) |
| { |
| if (pid->idtype == AT_AP_IDValue) |
| { |
| TupleDesc tupledesc = RelationGetDescr(rel); |
| bool *isnull; |
| PartitionNode *pNode2 = NULL; |
| Datum *d = magic_expr_to_datum(rel, pNode, |
| pid->partiddef, &isnull); |
| |
| /* MPP-4011: get right pid for FOR(value). pass a pNode |
| * ptr down to prevent recursion in selectPartition -- we |
| * only want the top-most partition for the value in this |
| * case |
| */ |
| if (ppNode) |
| partrelid = selectPartition1(pNode, d, isnull, tupledesc, NULL, |
| pSearch, ppNode); |
| else |
| partrelid = selectPartition1(pNode, d, isnull, tupledesc, NULL, |
| pSearch, &pNode2); |
| |
| /* build a string rep for the value */ |
| { |
| ParseState *pstate = make_parsestate(NULL); |
| Node *pval = (Node *)pid->partiddef; |
| char *idval = NULL; |
| |
| pval = (Node *)transformExpressionList(pstate, |
| (List *)pval); |
| |
| free_parsestate(&pstate); |
| |
| idval = |
| deparse_expression(pval, |
| deparse_context_for(RelationGetRelationName(rel), |
| RelationGetRelid(rel)), |
| false, false); |
| |
| if (idval) |
| snprintf(partIdStr, sizeof(partIdStr), |
| " for value (%s)", |
| idval); |
| } |
| |
| } |
| else if (pid->idtype == AT_AP_IDRank) |
| { |
| char *parTypName = "UNKNOWN"; |
| |
| if (pNode->part->parkind != 'r') |
| { |
| switch (pNode->part->parkind) |
| { |
| case 'h': /* hash */ |
| parTypName = "HASH"; |
| break; |
| case 'l': /* list */ |
| parTypName = "LIST"; |
| break; |
| } /* end switch */ |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("cannot find partition by RANK -- " |
| "%s is %s partitioned", |
| relname, |
| parTypName), |
| errOmitLocation(true))); |
| |
| } |
| |
| partrelid = selectPartitionByRank(pNode, idrank); |
| } |
| } |
| |
| /* check thru the list of partition rules to match by relid or name */ |
| if (pNode) |
| { |
| ListCell *lc; |
| int rulerank = 1; |
| |
| /* set up the relid for the default partition if necessary */ |
| if ((pid->idtype == AT_AP_IDDefault) |
| && pNode->default_part) |
| partrelid = pNode->default_part->parchildrelid; |
| |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| bool foundit = false; |
| |
| if ((pid->idtype == AT_AP_IDValue) |
| || (pid->idtype == AT_AP_IDRank)) |
| { |
| if ((partrelid != InvalidOid) |
| && (partrelid == rule->parchildrelid)) |
| { |
| foundit = true; |
| |
| if (strlen(rule->parname)) |
| { |
| snprintf(partIdStr, sizeof(partIdStr), " \"%s\"", |
| rule->parname); |
| snprintf(namBuf, sizeof(namBuf), "%s", |
| rule->parname); |
| } |
| } |
| } |
| else if (pid->idtype == AT_AP_IDName) |
| { |
| if (0 == strcmp(rule->parname, namBuf)) |
| foundit = true; |
| } |
| |
| if (foundit) |
| { |
| prule = makeNode(PgPartRule); |
| |
| prule->pNode = pNode; |
| prule->topRule = rule; |
| prule->topRuleRank = rulerank; /* 1-based */ |
| prule->relname = relname; |
| break; |
| } |
| rulerank++; |
| } /* end foreach */ |
| |
| /* if cannot find, check default partition */ |
| if (!prule && pNode->default_part) |
| { |
| PartitionRule *rule = pNode->default_part; |
| bool foundit = false; |
| |
| if ((pid->idtype == AT_AP_IDValue) |
| || (pid->idtype == AT_AP_IDRank) |
| || (pid->idtype == AT_AP_IDDefault)) |
| { |
| if ((partrelid != InvalidOid) |
| && (partrelid == rule->parchildrelid)) |
| { |
| foundit = true; |
| |
| if (strlen(rule->parname)) |
| { |
| snprintf(partIdStr, sizeof(partIdStr), " \"%s\"", |
| rule->parname); |
| snprintf(namBuf, sizeof(namBuf), "%s", |
| rule->parname); |
| } |
| } |
| } |
| else if (pid->idtype == AT_AP_IDName) |
| { |
| if (0 == strcmp(rule->parname, namBuf)) |
| foundit = true; |
| } |
| |
| if (foundit) |
| { |
| prule = makeNode(PgPartRule); |
| |
| prule->pNode = pNode; |
| prule->topRule = rule; |
| prule->topRuleRank = 0; /* 1-based -- 0 means no rank */ |
| prule->relname = relname; |
| } |
| } |
| } /* end if pnode */ |
| |
| /* if the partition exists, set the "id string" in prule and |
| * indicate whether it is the partition name. The ATPExec |
| * commands will notify users of the "real" name if the original |
| * specification was by value or rank |
| */ |
| if (prule) |
| { |
| prule->partIdStr = pstrdup(partIdStr); |
| prule->isName = (strlen(namBuf) > 0); |
| } |
| |
| if (!bExistError) |
| goto L_fin_partrule; |
| |
| /* MPP-3722: complain if for(value) matches the default partition */ |
| if ((pid->idtype == AT_AP_IDValue) |
| && prule && |
| (prule->topRule == prule->pNode->default_part)) |
| { |
| if (bMustExist) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("FOR expression matches " |
| "DEFAULT partition%s of %s", |
| prule->isName ? |
| partIdStr : "", |
| relname), |
| errhint("FOR expression may only specify " |
| "a non-default partition in this context."), |
| errOmitLocation(true))); |
| |
| } |
| |
| if (bMustExist && !prule) |
| { |
| switch (pid->idtype) |
| { |
| case AT_AP_IDNone: /* no ID */ |
| /* should never happen */ |
| Assert(false); |
| break; |
| case AT_AP_IDName: /* IDentify by Name */ |
| case AT_AP_IDValue: /* IDentifier FOR Value */ |
| case AT_AP_IDRank: /* IDentifier FOR Rank */ |
| case AT_AP_ID_oid: /* IDentifier by oid */ |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("partition%s of %s does not exist", |
| partIdStr, |
| relname), |
| errOmitLocation(true))); |
| break; |
| case AT_AP_IDDefault: /* IDentify DEFAULT partition */ |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("DEFAULT partition of %s does not exist", |
| relname), |
| errOmitLocation(true))); |
| break; |
| default: /* XXX XXX */ |
| Assert(false); |
| |
| } |
| } |
| else if (!bMustExist && prule) |
| { |
| switch (pid->idtype) |
| { |
| case AT_AP_IDNone: /* no ID */ |
| /* should never happen */ |
| Assert(false); |
| break; |
| case AT_AP_IDName: /* IDentify by Name */ |
| case AT_AP_IDValue: /* IDentifier FOR Value */ |
| case AT_AP_IDRank: /* IDentifier FOR Rank */ |
| case AT_AP_ID_oid: /* IDentifier by oid */ |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("partition%s of %s already exists", |
| partIdStr, |
| relname), |
| errOmitLocation(true))); |
| break; |
| case AT_AP_IDDefault: /* IDentify DEFAULT partition */ |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("DEFAULT partition%s of %s already exists", |
| prule->isName ? |
| partIdStr : "", |
| relname), |
| errOmitLocation(true))); |
| break; |
| default: /* XXX XXX */ |
| Assert(false); |
| |
| } |
| |
| } |
| |
| L_fin_partrule: |
| |
| return prule; |
| } /* end get_part_rule1 */ |
| |
| PgPartRule * |
| get_part_rule(Relation rel, |
| AlterPartitionId *pid, |
| bool bExistError, |
| bool bMustExist, |
| MemoryContext mcxt, |
| int *pSearch, |
| bool inctemplate) |
| { |
| PartitionNode *pNode = NULL; |
| PartitionNode *pNode2 = NULL; |
| char relnamBuf[(NAMEDATALEN * 2)]; |
| char *relname; |
| |
| snprintf(relnamBuf, sizeof(relnamBuf), "relation \"%s\"", |
| RelationGetRelationName(rel)); |
| |
| relname = pstrdup(relnamBuf); |
| |
| pNode = RelationBuildPartitionDesc(rel, inctemplate); |
| |
| if (pid && ((pid->idtype != AT_AP_IDList) |
| && (pid->idtype != AT_AP_IDRule))) |
| return get_part_rule1(rel, |
| pid, |
| bExistError, bMustExist, |
| mcxt, pSearch, pNode, relname, NULL); |
| |
| if (!pid) |
| return NULL; |
| |
| if (pid->idtype == AT_AP_IDRule) |
| { |
| List *l1 = (List *)pid->partiddef; |
| ListCell *lc; |
| AlterPartitionId *pid2 = NULL; |
| PgPartRule* prule2 = NULL; |
| StringInfoData sid1, sid2; |
| |
| initStringInfo(&sid1); |
| initStringInfo(&sid2); |
| |
| lc = list_head(l1); |
| prule2 = (PgPartRule*) lfirst(lc); |
| if (prule2 && prule2->topRule && prule2->topRule->children) |
| pNode = prule2->topRule->children; |
| |
| truncateStringInfo(&sid1, 0); |
| appendStringInfo(&sid1, "%s", prule2->relname); |
| |
| lc = lnext(lc); |
| |
| pid2 = (AlterPartitionId *)lfirst(lc); |
| |
| prule2 = get_part_rule1(rel, |
| pid2, |
| bExistError, bMustExist, |
| mcxt, pSearch, pNode, sid1.data, &pNode2); |
| |
| pNode = pNode2; |
| |
| if (!pNode) |
| { |
| if (prule2 && prule2->topRule && prule2->topRule->children) |
| pNode = prule2->topRule->children; |
| } |
| |
| return prule2; |
| } |
| |
| if (pid->idtype == AT_AP_IDList) |
| { |
| List *l1 = (List *)pid->partiddef; |
| ListCell *lc; |
| AlterPartitionId *pid2 = NULL; |
| PgPartRule* prule2 = NULL; |
| StringInfoData sid1, sid2; |
| |
| initStringInfo(&sid1); |
| initStringInfo(&sid2); |
| appendStringInfoString(&sid1, relnamBuf); |
| |
| foreach(lc, l1) |
| { |
| |
| pid2 = (AlterPartitionId *)lfirst(lc); |
| |
| prule2 = get_part_rule1(rel, |
| pid2, |
| bExistError, bMustExist, |
| mcxt, pSearch, pNode, sid1.data, &pNode2); |
| |
| pNode = pNode2; |
| |
| if (!pNode) |
| { |
| if (prule2 && prule2->topRule && prule2->topRule->children) |
| pNode = prule2->topRule->children; |
| } |
| |
| appendStringInfo(&sid2, "partition%s of %s", |
| prule2->partIdStr, sid1.data); |
| truncateStringInfo(&sid1, 0); |
| appendStringInfo(&sid1, "%s", sid2.data); |
| truncateStringInfo(&sid2, 0); |
| } /* end foreach */ |
| |
| return prule2; |
| } |
| return NULL; |
| } /* end get_part_rule */ |
| |
| static void |
| fixup_table_storage_options(CreateStmt *ct) |
| { |
| if (!ct->base.options) |
| { |
| ct->base.options = list_make2(makeDefElem("appendonly", |
| (Node *)makeString("true")), |
| makeDefElem("orientation", |
| (Node *)makeString("column"))); |
| } |
| } |
| |
| /* |
| * The user is adding a partition and we have a subpartition template that has |
| * been brought into the mix. At partition create time, if the user added any |
| * storage encodings to the subpartition template, we would have cached them in |
| * pg_partition_encoding. Retrieve them now, deparse and apply to the |
| * PartitionSpec as if this was CREATE TABLE. |
| */ |
| static void |
| apply_template_storage_encodings(CreateStmt *ct, Oid relid, Oid paroid, |
| PartitionSpec *tmpl) |
| { |
| List *encs = get_deparsed_partition_encodings(relid, paroid); |
| |
| if (encs) |
| { |
| /* |
| * If the user didn't specify WITH (...) at create time, |
| * we need to force the new partitions to be AO/CO. |
| */ |
| fixup_table_storage_options(ct); |
| tmpl->partElem = list_concat(tmpl->partElem, |
| encs); |
| } |
| } |
| |
| /* |
| * atpxPart_validate_spec: kludge up a PartitionBy statement and use |
| * validate_partition_spec to transform and validate a partition |
| * boundary spec. |
| */ |
| |
| static int |
| atpxPart_validate_spec( |
| PartitionBy *pBy, |
| CreateStmtContext *pcxt, |
| Relation rel, |
| CreateStmt *ct, |
| PartitionElem *pelem, |
| PartitionNode *pNode, |
| Node *partName, |
| bool isDefault, |
| PartitionByType part_type, |
| char *partDesc) |
| { |
| PartitionSpec *spec = makeNode(PartitionSpec); |
| ParseState *pstate = NULL; |
| List *schema = NIL; |
| List *inheritOids; |
| List *old_constraints; |
| int parentOidCount; |
| int result; |
| PartitionNode *pNode_tmpl = NULL; |
| |
| /* get the table column defs */ |
| schema = |
| MergeAttributes(schema, |
| list_make1( |
| makeRangeVar( |
| NULL /*catalogname*/, |
| get_namespace_name( |
| RelationGetNamespace(rel)), |
| pstrdup(RelationGetRelationName(rel)), -1)), |
| false, true /* isPartitioned */, |
| &inheritOids, &old_constraints, &parentOidCount, NULL); |
| |
| pcxt->columns = schema; |
| |
| spec->partElem = list_make1(pelem); |
| |
| pelem->partName = partName ? copyObject(partName) : NULL; |
| pelem->isDefault = isDefault; |
| pelem->AddPartDesc = pstrdup(partDesc); |
| |
| /* generate a random name for the partition relation if necessary */ |
| if (partName) |
| pelem->rrand = 0; |
| else |
| pelem->rrand = random(); |
| |
| pBy->partType = part_type; |
| pBy->keys = NULL; |
| pBy->partNum = 0; |
| pBy->subPart = NULL; |
| pBy->partSpec = (Node *)spec; |
| pBy->partDepth = pNode->part->parlevel; |
| /* Note: pBy->partQuiet already set by caller */ |
| pBy->parentRel = |
| makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)), |
| pstrdup(RelationGetRelationName(rel)), -1); |
| pBy->location = -1; |
| pBy->partDefault = NULL; |
| pBy->bKeepMe = true; /* 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 |
| */ |
| |
| /* fixup the pnode_tmpl to get the right parlevel */ |
| if (pNode && (pNode->rules || pNode->default_part)) |
| { |
| PartitionRule *prule; |
| |
| if (pNode->default_part) |
| prule = pNode->default_part; |
| else |
| prule = linitial(pNode->rules); |
| |
| pNode_tmpl = get_parts( |
| pNode->part->parrelid, |
| pNode->part->parlevel + 1, |
| InvalidOid, /* no parent for template */ |
| true, |
| CurrentMemoryContext, |
| true /*includesubparts*/ |
| ); |
| } |
| |
| { /* find the partitioning keys (recursively) */ |
| |
| PartitionBy *pBy2 = pBy; |
| PartitionBy *parent_pBy2 = NULL; |
| PartitionNode *pNode2 = pNode; |
| |
| int ii; |
| TupleDesc tupleDesc = RelationGetDescr(rel); |
| List *pbykeys = NIL; |
| List *pbyopclass = NIL; |
| Oid accessMethodId = BTREE_AM_OID; |
| |
| while (pNode2) |
| { |
| pbykeys = NIL; |
| pbyopclass = NIL; |
| |
| for (ii = 0; ii < pNode2->part->parnatts; ii++) |
| { |
| AttrNumber attno = |
| pNode2->part->paratts[ii]; |
| Form_pg_attribute attribute = |
| tupleDesc->attrs[attno - 1]; |
| char *attributeName = |
| NameStr(attribute->attname); |
| Oid opclass = |
| InvalidOid; |
| |
| opclass = |
| GetDefaultOpClass(attribute->atttypid, accessMethodId); |
| |
| if (pbykeys) |
| { |
| pbykeys = lappend(pbykeys, makeString(attributeName)); |
| pbyopclass = lappend_oid(pbyopclass, opclass); |
| } |
| else |
| { |
| pbykeys = list_make1(makeString(attributeName)); |
| pbyopclass = list_make1_oid(opclass); |
| } |
| } /* end for */ |
| |
| pBy2->keys = pbykeys; |
| pBy2->keyopclass = pbyopclass; |
| |
| if (parent_pBy2) |
| parent_pBy2->subPart = (Node *)pBy2; |
| |
| parent_pBy2 = pBy2; |
| |
| if (pNode2 && (pNode2->rules || pNode2->default_part)) |
| { |
| PartitionRule *prule; |
| PartitionElem *el = NULL; /* for the subpartn template */ |
| |
| if (pNode2->default_part) |
| prule = pNode2->default_part; |
| else |
| prule = linitial(pNode2->rules); |
| |
| if (prule && prule->children) |
| { |
| pNode2 = prule->children; |
| |
| /* XXX XXX: make this work for HASH at some point */ |
| |
| Assert( |
| ('l' == pNode2->part->parkind) || |
| ('r' == pNode2->part->parkind)); |
| |
| pBy2 = makeNode(PartitionBy); |
| pBy2->partType = |
| ('r' == pNode2->part->parkind) ? |
| PARTTYP_RANGE : |
| PARTTYP_LIST; |
| pBy2->keys = NULL; |
| pBy2->partNum = 0; |
| pBy2->subPart = NULL; |
| pBy2->partSpec = NULL; |
| pBy2->partDepth = pNode2->part->parlevel; |
| pBy2->partQuiet = pBy->partQuiet; |
| pBy2->parentRel = |
| makeRangeVar( |
| NULL /*catalogname*/, |
| get_namespace_name( |
| RelationGetNamespace(rel)), |
| pstrdup(RelationGetRelationName(rel)), -1); |
| pBy2->location = -1; |
| pBy2->partDefault = NULL; |
| |
| el = NULL; |
| |
| /* build the template (if it exists) */ |
| if (pNode_tmpl) |
| { |
| PartitionSpec *spec_tmpl = makeNode(PartitionSpec); |
| ListCell *lc; |
| |
| spec_tmpl->istemplate = true; |
| |
| /* add entries for rules at current level */ |
| foreach(lc, pNode_tmpl->rules) |
| { |
| PartitionRule *rule_tmpl = lfirst(lc); |
| |
| el = makeNode(PartitionElem); |
| |
| if (rule_tmpl->parname && |
| strlen(rule_tmpl->parname)) |
| el->partName = |
| (Node*)makeString(rule_tmpl->parname); |
| |
| el->isDefault = rule_tmpl->parisdefault; |
| |
| /* MPP-6904: use storage options from template */ |
| if (rule_tmpl->parreloptions || |
| rule_tmpl->partemplatespaceId) |
| { |
| Node *tspaceName = NULL; |
| AlterPartitionCmd *apc = |
| makeNode(AlterPartitionCmd); |
| |
| el->storeAttr = (Node *)apc; |
| |
| if (rule_tmpl->partemplatespaceId) |
| tspaceName = |
| (Node*)makeString( |
| get_tablespace_name( |
| rule_tmpl->partemplatespaceId |
| )); |
| |
| apc->partid = NULL; |
| apc->arg2 = tspaceName; |
| apc->arg1 = (Node *)rule_tmpl->parreloptions; |
| |
| } |
| |
| /* LIST */ |
| if (rule_tmpl->parlistvalues) |
| { |
| PartitionValuesSpec *vspec = |
| makeNode(PartitionValuesSpec); |
| |
| el->boundSpec = (Node*)vspec; |
| |
| vspec->partValues = rule_tmpl->parlistvalues; |
| } |
| |
| /* RANGE */ |
| if (rule_tmpl->parrangestart || |
| rule_tmpl->parrangeend) |
| { |
| PartitionBoundSpec *bspec = |
| makeNode(PartitionBoundSpec); |
| PartitionRangeItem *ri; |
| |
| if (rule_tmpl->parrangestart) |
| { |
| ri = |
| makeNode(PartitionRangeItem); |
| |
| ri->partedge = |
| rule_tmpl->parrangestartincl ? |
| PART_EDGE_INCLUSIVE : |
| PART_EDGE_EXCLUSIVE ; |
| |
| ri->partRangeVal = |
| (List *)rule_tmpl->parrangestart; |
| |
| bspec->partStart = (Node*)ri; |
| |
| } |
| if (rule_tmpl->parrangeend) |
| { |
| ri = |
| makeNode(PartitionRangeItem); |
| |
| ri->partedge = |
| rule_tmpl->parrangeendincl ? |
| PART_EDGE_INCLUSIVE : |
| PART_EDGE_EXCLUSIVE ; |
| |
| ri->partRangeVal = |
| (List *)rule_tmpl->parrangeend; |
| |
| bspec->partEnd = (Node*)ri; |
| |
| } |
| if (rule_tmpl->parrangeevery) |
| { |
| ri = |
| makeNode(PartitionRangeItem); |
| |
| ri->partRangeVal = |
| (List *)rule_tmpl->parrangeevery; |
| |
| bspec->partEvery = (Node*)ri; |
| |
| } |
| |
| el->boundSpec = (Node*)bspec; |
| |
| } /* end if RANGE */ |
| |
| spec_tmpl->partElem = lappend(spec_tmpl->partElem, |
| el); |
| } /* end foreach */ |
| |
| /* MPP-4725 */ |
| /* and the default partition */ |
| if (pNode_tmpl->default_part) |
| { |
| PartitionRule *rule_tmpl = |
| pNode_tmpl->default_part; |
| |
| el = makeNode(PartitionElem); |
| |
| if (rule_tmpl->parname && |
| strlen(rule_tmpl->parname)) |
| el->partName = |
| (Node*)makeString(rule_tmpl->parname); |
| |
| el->isDefault = rule_tmpl->parisdefault; |
| |
| spec_tmpl->partElem = lappend(spec_tmpl->partElem, |
| el); |
| |
| } |
| |
| /* apply storage encoding for this template */ |
| apply_template_storage_encodings(ct, |
| RelationGetRelid(rel), |
| pNode_tmpl->part->partid, |
| spec_tmpl); |
| |
| /* the PartitionElem should hang off the pby |
| * partspec, and subsequent templates should |
| * hang off the subspec for the prior |
| * PartitionElem. |
| */ |
| |
| pBy2->partSpec = (Node *)spec_tmpl; |
| |
| } /* end if pNode_tmpl */ |
| |
| /* fixup the pnode_tmpl to get the right parlevel */ |
| if (pNode2 && (pNode2->rules || pNode2->default_part)) |
| { |
| PartitionRule *prule66; |
| |
| if (pNode2->default_part) |
| prule66 = pNode2->default_part; |
| else |
| prule66 = linitial(pNode2->rules); |
| |
| pNode_tmpl = get_parts( |
| pNode2->part->parrelid, |
| pNode2->part->parlevel + 1, |
| InvalidOid, /* no parent for template */ |
| true, |
| CurrentMemoryContext, |
| true /*includesubparts*/ |
| ); |
| } |
| |
| } |
| else |
| pNode2 = NULL; |
| } |
| else |
| pNode2 = NULL; |
| |
| } /* end while */ |
| } |
| |
| pstate = make_parsestate(NULL); |
| result = validate_partition_spec(pstate, pcxt, ct, pBy, "", -1); |
| free_parsestate(&pstate); |
| |
| return result; |
| } /* end atpxPart_validate_spec */ |
| |
| Node * |
| atpxPartAddList(Relation rel, |
| AlterPartitionCmd *pc, |
| PartitionNode *pNode, |
| Node *pUtl, /* pc2->arg2 */ |
| Node *partName, /* pid->partiddef (or NULL) */ |
| bool isDefault, |
| PartitionElem *pelem, |
| PartitionByType part_type, |
| PgPartRule* par_prule, |
| char *lrelname, |
| bool bSetTemplate, |
| Oid ownerid) |
| { |
| CreateStmt *ct = NULL; |
| DestReceiver *dest = None_Receiver; |
| int partno = 0; |
| int maxpartno = 0; |
| bool bOpenGap = false; |
| PartitionBy *pBy = makeNode(PartitionBy); |
| CreateStmtContext cxt; |
| Node *pSubSpec = NULL; /* return the subpartition spec */ |
| Relation par_rel = rel; |
| PartitionNode pNodebuf; |
| PartitionNode *pNode2 = &pNodebuf; |
| AlterPartitionCmd *pc2 = (AlterPartitionCmd *)pc->arg2; |
| bool is_split = PointerIsValid(pc2->partid); |
| |
| /* get the relation for the parent of the new partition */ |
| if (par_prule && par_prule->topRule) |
| par_rel = |
| heap_open(par_prule->topRule->parchildrelid, AccessShareLock); |
| |
| MemSet(&cxt, 0, sizeof(cxt)); |
| |
| Assert( (PARTTYP_LIST == part_type) || (PARTTYP_RANGE == part_type) ); |
| |
| /* ct - the CreateStmt from pUtl ammended to show that it is for an |
| * added part, that it is owned by the argument ownerid, and that it |
| * is distributed like the parent rel. Note that, at this time, |
| * the name is "fake_partition_name". */ |
| |
| Assert(IsA(pUtl, List)); |
| |
| ct = (CreateStmt *)linitial((List *)pUtl); |
| Assert(IsA(ct, CreateStmt)); |
| |
| ct->base.is_add_part = true; /* subroutines need to know this */ |
| ct->ownerid = ownerid; |
| |
| if (!ct->base.distributedBy) |
| ct->base.distributedBy = make_dist_clause(rel); |
| |
| if (bSetTemplate) |
| /* if creating a template, silence partition name messages */ |
| pBy->partQuiet = PART_VERBO_NOPARTNAME; |
| else |
| /* just silence distribution policy messages */ |
| pBy->partQuiet = PART_VERBO_NODISTRO; |
| |
| /* XXX XXX: handle case of missing boundary spec for range with EVERY */ |
| |
| if (pelem && pelem->boundSpec) |
| { |
| if (PARTTYP_RANGE == part_type) |
| { |
| PartitionBoundSpec *pbs = NULL; |
| PgPartRule *prule = NULL; |
| AlterPartitionId pid; |
| ParseState *pstate = make_parsestate(NULL); |
| TupleDesc tupledesc = RelationGetDescr(rel); |
| |
| MemSet(&pid, 0, sizeof(AlterPartitionId)); |
| |
| pid.idtype = AT_AP_IDRank; |
| pid.location = -1; |
| |
| Assert (IsA(pelem->boundSpec, PartitionBoundSpec)); |
| |
| pbs = (PartitionBoundSpec *)pelem->boundSpec; |
| pSubSpec = pelem->subSpec; /* look for subpartition spec */ |
| |
| /* no EVERY */ |
| if (pbs->partEvery) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot specify EVERY when adding " |
| "RANGE partition to %s", |
| lrelname), |
| errOmitLocation(true))); |
| |
| if (!(pbs->partStart || pbs->partEnd )) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("Need START or END when adding " |
| "RANGE partition to %s", |
| lrelname), |
| errOmitLocation(true))); |
| |
| /* if no START, then START after last partition */ |
| |
| if (!(pbs->partStart)) |
| { |
| Datum *d_end = NULL; |
| bool *isnull; |
| bool bstat; |
| |
| pid.partiddef = (Node *)makeInteger(-1); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| /* ok if no prior -- just means this is first |
| * partition (XXX XXX though should always have 1 |
| * partition in the table...) |
| */ |
| |
| if (!(prule && prule->topRule)) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| goto L_fin_no_start; |
| } |
| |
| { |
| Node *n1; |
| |
| if ( !IsA(pbs->partEnd, PartitionRangeItem) ) |
| { |
| /* pbs->partEnd isn't a PartitionRangeItem! This probably means |
| * an invalid split of a default part, but we aren't really sure. |
| * See MPP-14613. |
| */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("invalid partition range specification."), |
| errOmitLocation(true))); |
| } |
| |
| PartitionRangeItem *ri = |
| (PartitionRangeItem *)pbs->partEnd; |
| PartitionRangeItemIsValid(NULL, ri); |
| |
| n1 = (Node *)copyObject(ri->partRangeVal); |
| n1 = (Node *)transformExpressionList(pstate, |
| (List *)n1); |
| |
| d_end = |
| magic_expr_to_datum(rel, pNode, |
| n1, &isnull); |
| } |
| |
| if (prule && prule->topRule && prule->topRule->parrangeend |
| && list_length((List *)prule->topRule->parrangeend)) |
| { |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)prule->topRule->parrangeend, |
| d_end, isnull, tupledesc); |
| |
| /* if the current end is less than the new end |
| * then use it as the start of the new |
| * partition |
| */ |
| |
| if (bstat) |
| { |
| PartitionRangeItem *ri = makeNode(PartitionRangeItem); |
| |
| ri->location = -1; |
| |
| ri->partRangeVal = |
| copyObject(prule->topRule->parrangeend); |
| |
| /* invert the inclusive/exclusive */ |
| ri->partedge = prule->topRule->parrangeendincl ? |
| PART_EDGE_EXCLUSIVE : |
| PART_EDGE_INCLUSIVE; |
| |
| /* should be final partition */ |
| maxpartno = prule->topRule->parruleord + 1; |
| |
| pbs->partStart = (Node *)ri; |
| goto L_fin_no_start; |
| } |
| } |
| |
| /* if the last partition doesn't have an end, or the |
| * end isn't less than the new end, check if new end |
| * is less than current start |
| */ |
| |
| pid.partiddef = (Node *)makeInteger(1); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| if (!(prule && prule->topRule && prule->topRule->parrangestart |
| && list_length((List *)prule->topRule->parrangestart))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("new partition overlaps existing " |
| "partition"), |
| errOmitLocation(true))); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)prule->topRule->parrangestart, |
| d_end, isnull, tupledesc); |
| |
| |
| if (!bstat) |
| { |
| /* |
| * MPP-13806 fix by Gavin Sherry |
| * |
| * As we support explicit inclusive and exclusive ranges |
| * we need to be even more careful. |
| * |
| * We can proceed if we have the following: |
| * |
| * END (R) EXCLUSIVE ; START (R) INCLUSIVE |
| * END (R) INCLUSIVE ; START (R) EXCLUSIVE |
| * |
| * XXX: this should be refactored into a single generic |
| * function that can be used here and in the unbounded end |
| * case, checked further down. That said, a lot of this code |
| * should be refactored. |
| */ |
| PartitionRangeItem *ri = (PartitionRangeItem *)pbs->partEnd; |
| |
| if ((ri->partedge == PART_EDGE_EXCLUSIVE && |
| prule->topRule->parrangestartincl) || |
| (ri->partedge == PART_EDGE_INCLUSIVE && |
| !prule->topRule->parrangestartincl)) |
| { |
| bstat = compare_partn_opfuncid(pNode, "pg_catalog", "=", |
| (List *)prule->topRule->parrangestart, |
| d_end, isnull, tupledesc); |
| } |
| |
| } |
| |
| if (bstat) |
| { |
| /* should be first partition */ |
| maxpartno = prule->topRule->parruleord - 1; |
| if (0 == maxpartno) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| } |
| |
| } |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("new partition overlaps existing " |
| "partition"), |
| errOmitLocation(true))); |
| |
| L_fin_no_start: |
| bstat = false; /* fix warning */ |
| |
| } |
| else if (!(pbs->partEnd)) |
| { /* if no END, then END before first partition |
| **ONLY IF** |
| * START of this partition is before first partition ... */ |
| |
| Datum *d_start = NULL; |
| bool *isnull; |
| bool bstat; |
| |
| pid.partiddef = (Node *)makeInteger(1); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| /* NOTE: invert all the logic of case of missing partStart */ |
| |
| /* ok if no successor [?] -- just means this is first |
| * partition (XXX XXX though should always have 1 |
| * partition in the table... [XXX XXX unless did a |
| * SPLIT of a single partition !! ]) |
| */ |
| |
| if (!(prule && prule->topRule)) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| goto L_fin_no_end; |
| } |
| |
| { |
| Node *n1; |
| PartitionRangeItem *ri = |
| (PartitionRangeItem *)pbs->partStart; |
| |
| PartitionRangeItemIsValid(NULL, ri); |
| n1 = (Node *)copyObject(ri->partRangeVal); |
| n1 = (Node *)transformExpressionList(pstate, |
| (List *)n1); |
| |
| d_start = |
| magic_expr_to_datum(rel, pNode, |
| n1, &isnull); |
| } |
| |
| |
| if (prule && prule->topRule && prule->topRule->parrangestart |
| && list_length((List *)prule->topRule->parrangestart)) |
| { |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)prule->topRule->parrangestart, |
| d_start, isnull, tupledesc); |
| |
| /* if the current start is greater than the new start |
| * then use the current start as the end of the new |
| * partition |
| */ |
| |
| if (bstat) |
| { |
| PartitionRangeItem *ri = makeNode(PartitionRangeItem); |
| |
| ri->location = -1; |
| |
| ri->partRangeVal = |
| copyObject(prule->topRule->parrangestart); |
| |
| /* invert the inclusive/exclusive */ |
| ri->partedge = prule->topRule->parrangestartincl ? |
| PART_EDGE_EXCLUSIVE : |
| PART_EDGE_INCLUSIVE; |
| |
| /* should be first partition */ |
| maxpartno = prule->topRule->parruleord - 1; |
| if (0 == maxpartno) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| } |
| |
| pbs->partEnd = (Node *)ri; |
| goto L_fin_no_end; |
| } |
| } |
| |
| /* if the first partition doesn't have an start, or the |
| * start isn't greater than the new start, check if new start |
| * is greater than current end |
| */ |
| |
| pid.partiddef = (Node *)makeInteger(-1); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| if (!(prule && prule->topRule && prule->topRule->parrangeend |
| && list_length((List *)prule->topRule->parrangeend))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("new partition overlaps existing " |
| "partition"), |
| errOmitLocation(true))); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)prule->topRule->parrangeend, |
| d_start, isnull, tupledesc); |
| if (bstat) |
| { |
| /* should be final partition */ |
| maxpartno = prule->topRule->parruleord + 1; |
| } |
| else |
| { |
| PartitionRangeItem *ri = |
| (PartitionRangeItem *)pbs->partStart; |
| |
| /* check for equality */ |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)prule->topRule->parrangeend, |
| d_start, isnull, tupledesc); |
| |
| /* if new start not >= to current end, then |
| * new start < current end, so it overlaps. Or if |
| * new start == current end, but the |
| * inclusivity is not opposite for the boundaries |
| * (eg inclusive end abuts inclusive start for |
| * same start/end value) then it overlaps |
| */ |
| if (!bstat || |
| (bstat && |
| (prule->topRule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE))) |
| ) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("new partition overlaps existing " |
| "partition"), |
| errOmitLocation(true))); |
| |
| /* doesn't overlap, should be final partition */ |
| maxpartno = prule->topRule->parruleord + 1; |
| |
| } |
| L_fin_no_end: |
| bstat = false; /* fix warning */ |
| |
| } |
| else |
| { /* both start and end are specified */ |
| PartitionRangeItem *ri; |
| bool bOverlap = false; |
| int isMiddle = 0; /* -1 for new first, 1 for new last */ |
| bool *isnull; |
| int startSearchpoint; |
| int endSearchpoint; |
| Datum *d_start = NULL; |
| Datum *d_end = NULL; |
| |
| /* see if start or end overlaps */ |
| pid.idtype = AT_AP_IDValue; |
| |
| /* check the start */ |
| |
| ri = (PartitionRangeItem *)pbs->partStart; |
| PartitionRangeItemIsValid(NULL, ri); |
| |
| pid.partiddef = (Node *)copyObject(ri->partRangeVal); |
| pid.partiddef = |
| (Node *)transformExpressionList(pstate, |
| (List *)pid.partiddef); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, &startSearchpoint, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| /* found match for start value in rules */ |
| if (prule && !(prule->topRule->parisdefault && is_split)) |
| { |
| bool bstat; |
| PartitionRule *a_rule = prule->topRule; |
| d_start = |
| magic_expr_to_datum(rel, pNode, |
| pid.partiddef, &isnull); |
| |
| /* if start value was inclusive then it definitely |
| * overlaps |
| */ |
| if (ri->partedge == PART_EDGE_INCLUSIVE) |
| { |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* not inclusive -- check harder if START really |
| * overlaps |
| */ |
| |
| if (0 == |
| list_length((List *)a_rule->parrangeend)) |
| { |
| /* infinite end > new start - overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| if (bstat) |
| { |
| /* end > new start - overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* Must be the case that new start == end of |
| * a_rule (because if the end < new start then how |
| * could we find it in the interval for prule ?) |
| * This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE -> New partition does not |
| * overlap. |
| */ |
| |
| Assert (compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc)); |
| |
| if (a_rule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap |
| */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* opposite inclusive/exclusive, so in middle of |
| * range of existing partitions |
| */ |
| isMiddle = 0; |
| goto L_check_end; |
| } /* end if prule */ |
| |
| /* check for basic case of START > last partition */ |
| if (pNode && pNode->rules && list_length(pNode->rules)) |
| { |
| bool bstat; |
| PartitionRule *a_rule = /* get last rule */ |
| (PartitionRule *)list_nth(pNode->rules, |
| list_length(pNode->rules) - 1); |
| d_start = |
| magic_expr_to_datum(rel, pNode, |
| pid.partiddef, &isnull); |
| |
| if (0 == |
| list_length((List *)a_rule->parrangeend)) |
| { |
| /* infinite end > new start */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| |
| /* if the new partition start > end of the last |
| * partition then it is the new final partition. |
| * Don't bother checking the new end for overlap |
| * (just check if end > start in validation |
| * phase |
| */ |
| if (bstat) |
| { |
| isMiddle = 1; |
| |
| /* should be final partition */ |
| maxpartno = a_rule->parruleord + 1; |
| |
| goto L_end_overlap; |
| } |
| |
| /* could be the case that new start == end of |
| * last. This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE. New partition is still |
| * final partition for this case |
| */ |
| |
| if (0 == |
| list_length((List *)a_rule->parrangeend)) |
| { |
| /* infinite end > new start */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| if (bstat) |
| { |
| if (a_rule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| isMiddle = 1; |
| |
| /* should be final partition */ |
| maxpartno = a_rule->parruleord + 1; |
| |
| goto L_end_overlap; |
| } |
| else |
| { |
| /* tricky case: the new start is less than the |
| * end of the final partition, but it does not |
| * intersect any existing partitions. So we |
| * are trying to add a partition in the middle |
| * of the existing partitions or before the |
| * first partition. |
| */ |
| a_rule = /* get first rule */ |
| (PartitionRule *)list_nth(pNode->rules, 0); |
| |
| if (0 == |
| list_length((List *)a_rule->parrangestart)) |
| { |
| /* new start > negative infinite start */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)a_rule->parrangestart, |
| d_start, isnull, tupledesc); |
| |
| /* if the new partition start < start of the |
| * first partition then it is the new first |
| * partition. Check the new end for overlap. |
| * |
| * NOTE: ignore the case where |
| * new start == 1st start and |
| * inclusive vs exclusive because that is just |
| * stupid. |
| * |
| */ |
| if (bstat) |
| { |
| isMiddle = -1; |
| |
| /* should be first partition */ |
| maxpartno = a_rule->parruleord - 1; |
| if (0 == maxpartno) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| } |
| |
| } |
| else |
| { |
| isMiddle = 0; |
| } |
| } |
| } |
| else |
| { |
| /* if no "rules", then this is the first partition */ |
| |
| isMiddle = 1; |
| |
| /* should be final partition */ |
| maxpartno = 1; |
| |
| goto L_end_overlap; |
| } |
| |
| L_check_end: |
| /* check the end */ |
| /* check for basic case of END < first partition (the |
| opposite of START > last partition) */ |
| |
| ri = (PartitionRangeItem *)pbs->partEnd; |
| PartitionRangeItemIsValid(NULL, ri); |
| |
| pid.partiddef = (Node *)copyObject(ri->partRangeVal); |
| pid.partiddef = |
| (Node *)transformExpressionList(pstate, |
| (List *)pid.partiddef); |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, &endSearchpoint, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| /* found match for end value in rules */ |
| if (prule && !(prule->topRule->parisdefault && |
| is_split)) |
| { |
| bool bstat; |
| PartitionRule *a_rule = prule->topRule; |
| d_end = |
| magic_expr_to_datum(rel, pNode, |
| pid.partiddef, &isnull); |
| |
| /* if end value was inclusive then it definitely |
| * overlaps |
| */ |
| if (ri->partedge == PART_EDGE_INCLUSIVE) |
| { |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* not inclusive -- check harder if END really |
| * overlaps |
| */ |
| if (0 == |
| list_length((List *)a_rule->parrangestart)) |
| { |
| /* -infinite start < new end - overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| if (bstat) |
| { |
| /* start < new end - overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* Must be the case that new end = start of |
| * a_rule (because if the start > new end then how |
| * could we find it in the interval for prule ?) |
| * This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE -> New partition does not |
| * overlap. |
| */ |
| |
| Assert (compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc)); |
| |
| if (a_rule->parrangestartincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap |
| */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* opposite inclusive/exclusive, so in middle of |
| * range of existing partitions |
| */ |
| isMiddle = 0; |
| } /* end if prule */ |
| |
| |
| /* check for case of END < first partition */ |
| if (pNode && pNode->rules && list_length(pNode->rules)) |
| { |
| bool bstat; |
| PartitionRule *a_rule = /* get first rule */ |
| (PartitionRule *)list_nth(pNode->rules, 0); |
| d_end = |
| magic_expr_to_datum(rel, pNode, |
| pid.partiddef, &isnull); |
| |
| if (0 == |
| list_length((List *)a_rule->parrangestart)) |
| { |
| /* new end > negative infinite start */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| |
| /* if the new partition end < start of the first |
| * partition then it is the new first partition. |
| */ |
| if (bstat) |
| { |
| /* check if start was also ok for first partition */ |
| switch (isMiddle) |
| { |
| case -1: |
| /* since new start < first start and |
| * new end < first start should be |
| * first. |
| */ |
| |
| /* should be first partition */ |
| maxpartno = a_rule->parruleord - 1; |
| if (0 == maxpartno) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| } |
| |
| break; |
| case 0: |
| case 1: |
| default: |
| /* new end is less than first |
| * partition start but new start isn't |
| * -- must be end < start |
| */ |
| break; |
| } |
| goto L_end_overlap; |
| } |
| |
| /* could be the case that new end == start of |
| * first. This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE. New partition is still |
| * first partition for this case |
| */ |
| |
| if (0 == |
| list_length((List *)a_rule->parrangestart)) |
| { |
| /* new end > negative infinite start */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| if (bstat) |
| { |
| if (a_rule->parrangestartincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| /* check if start was also ok for first partition */ |
| switch (isMiddle) |
| { |
| case -1: |
| /* since new start < first start and |
| * new end < first start should be |
| * first. |
| */ |
| |
| /* should be first partition */ |
| maxpartno = a_rule->parruleord - 1; |
| if (0 == maxpartno) |
| { |
| maxpartno = 1; |
| bOpenGap = true; |
| } |
| |
| break; |
| case 0: |
| case 1: |
| default: |
| /* new end is less than first |
| * partition start but new start isn't |
| * -- must be end < start |
| */ |
| break; |
| } |
| goto L_end_overlap; |
| } |
| else |
| { |
| /* tricky case: the new end is greater than the |
| * start of the first partition, but it does not |
| * intersect any existing partitions. So we |
| * are trying to add a partition in the middle |
| * of the existing partitions or after the |
| * last partition. |
| */ |
| a_rule = /* get last rule */ |
| (PartitionRule *)list_nth(pNode->rules, |
| list_length(pNode->rules) - 1); |
| if (0 == |
| list_length((List *)a_rule->parrangeend)) |
| { |
| /* new end < infinite end */ |
| bstat = false; |
| } |
| else |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)a_rule->parrangeend, |
| d_end, isnull, tupledesc); |
| |
| /* if the new partition end > end of the |
| * last partition then it is the new last |
| * partition (maybe) |
| * |
| * NOTE: ignore the case where |
| * new end == last end and |
| * inclusive vs exclusive because that is just |
| * stupid. |
| * |
| */ |
| if (bstat) |
| { |
| switch (isMiddle) |
| { |
| case 1: |
| /* since new start > last end and |
| * new end > last end should be |
| * last. |
| */ |
| |
| /* should be last partition */ |
| maxpartno = a_rule->parruleord + 1; |
| |
| break; |
| case -1: |
| /* since new start < first start |
| * and new end > last end we would |
| * overlap all partitions!!! |
| */ |
| case 0: |
| /* since new start < last end |
| * and new end > last end we would |
| * overlap last partition |
| */ |
| bOverlap = true; |
| goto L_end_overlap; |
| break; |
| default: |
| /* new end is less than last |
| * partition end but new start isn't |
| * -- must be end < start |
| */ |
| break; |
| } |
| } |
| else |
| { |
| switch (isMiddle) |
| { |
| case -1: |
| /* since new start < first start |
| * and new end in middle we overlap |
| */ |
| bOverlap = true; |
| goto L_end_overlap; |
| |
| break; |
| case 0: |
| /* both start and end in middle */ |
| break; |
| case 1: |
| default: |
| /* since new start > last end and |
| * new end in middle |
| * -- must be end < start |
| */ |
| break; |
| } |
| } |
| } |
| } |
| else |
| { |
| /* if no "rules", then this is the first partition */ |
| |
| isMiddle = 1; |
| |
| /* should be final partition */ |
| maxpartno = 1; |
| |
| goto L_end_overlap; |
| } |
| |
| |
| /* if the individual start and end values don't |
| * intersect an existing partition, make sure they |
| * don't define a range which contains an existing |
| * partition, ie new start < existing start and new |
| * end > existing end |
| */ |
| |
| if (!bOverlap && (0 == isMiddle)) |
| { |
| bOpenGap = true; |
| |
| /* |
| hmm, not always true. see MPP-3667, MPP-3636, MPP-3593 |
| |
| if (startSearchpoint != endSearchpoint) |
| { |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| */ |
| while (1) |
| { |
| bool bstat; |
| PartitionRule *a_rule = /* get the rule */ |
| (PartitionRule *)list_nth(pNode->rules, |
| startSearchpoint); |
| |
| /* MPP-3621: fix ADD for open intervals */ |
| |
| if (0 == |
| list_length((List *)a_rule->parrangeend)) |
| { |
| /* new end < infinite end */ |
| bstat = false; |
| } |
| else |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<=", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| |
| if (bstat) |
| { |
| startSearchpoint++; |
| Assert(startSearchpoint |
| <= list_length(pNode->rules)); |
| continue; |
| } |
| |
| /* if previous partition was less than |
| current, then this one should be larger. |
| if not, then it overlaps... |
| */ |
| if ( |
| (0 == |
| list_length((List *)a_rule->parrangestart)) |
| || |
| !compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">=", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc)) |
| { |
| prule = NULL; /* could get the right prule... */ |
| bOverlap = true; |
| goto L_end_overlap; |
| } |
| |
| /* shift a_rule up so new rule has a place to fit */ |
| maxpartno = a_rule->parruleord; |
| |
| break; |
| } /* end while */ |
| |
| } /* end 0 == middle */ |
| |
| L_end_overlap: |
| if (bOverlap) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("new partition overlaps existing " |
| "partition%s", |
| (prule && prule->isName) ? |
| prule->partIdStr : ""), |
| errOmitLocation(true))); |
| |
| } |
| |
| free_parsestate(&pstate); |
| } /* end if parttype_range */ |
| } /* end if pelem && pelem->boundspec */ |
| |
| /* this function does transformExpr on the boundary specs */ |
| partno = atpxPart_validate_spec(pBy, |
| &cxt, |
| rel, |
| ct, |
| pelem, |
| pNode, |
| partName, |
| isDefault, |
| part_type, |
| ""); |
| |
| if (pelem && pelem->boundSpec) |
| { |
| if (PARTTYP_LIST == part_type) |
| { |
| ListCell *lc; |
| PartitionValuesSpec *spec; |
| AlterPartitionId pid; |
| |
| Assert (IsA(pelem->boundSpec, PartitionValuesSpec)); |
| |
| MemSet(&pid, 0, sizeof(AlterPartitionId)); |
| |
| spec = (PartitionValuesSpec *)pelem->boundSpec; |
| |
| /* only check this if we aren't doing split */ |
| if (1) |
| { |
| foreach(lc, spec->partValues) |
| { |
| List *vals = lfirst(lc); |
| PgPartRule *prule = NULL; |
| |
| pid.idtype = AT_AP_IDValue; |
| pid.partiddef = (Node *)vals; |
| pid.location = -1; |
| |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| if (prule && !(prule->topRule->parisdefault && is_split)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("new partition definition overlaps " |
| "existing partition%s of %s", |
| prule->partIdStr, |
| lrelname), |
| errOmitLocation(true))); |
| |
| } /* end foreach */ |
| } |
| |
| /* give a new maxpartno for the list partition */ |
| if (pNode && pNode->rules && list_length(pNode->rules)) |
| { |
| ListCell *lc; |
| |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| |
| /* find the highest parruleord */ |
| if (rule->parruleord > maxpartno) |
| maxpartno = rule->parruleord; |
| } |
| maxpartno++; |
| } |
| |
| } |
| |
| } |
| |
| /* create the partition - change the table name from "fake_partition_name" to |
| * name of the parent relation |
| */ |
| |
| ct->base.relation = makeRangeVar( |
| NULL /*catalogname*/, |
| get_namespace_name(RelationGetNamespace(par_rel)), |
| RelationGetRelationName(par_rel), -1); |
| |
| if (bOpenGap) /* might need a gap to insert the new partition */ |
| { |
| AlterPartitionId pid; |
| PgPartRule *prule = NULL; |
| |
| pid.idtype = AT_AP_IDRank; |
| pid.partiddef = (Node *)makeInteger(-1); |
| pid.location = -1; |
| |
| /* get the last rule, and increment each parruleord until |
| * reach parruleord == maxpartno |
| */ |
| prule = get_part_rule1(rel, &pid, false, false, |
| CurrentMemoryContext, NULL, |
| pNode, |
| lrelname, |
| &pNode2); |
| |
| /* NOTE: this assert is valid, except for the case of splitting |
| * the very last partition of a table. For that case, we must |
| * drop the last partition before re-adding the new pieces, which |
| * violates this invariant |
| */ |
| /* Assert(prule && prule->topRule && prule->pNode && prule->pNode->part); */ |
| |
| if (prule && prule->topRule && prule->pNode && prule->pNode->part) |
| parruleord_open_gap( |
| prule->pNode->part->partid, |
| prule->pNode->part->parlevel, |
| prule->topRule->parparentoid, |
| prule->topRule->parruleord, |
| maxpartno, |
| CurrentMemoryContext); |
| } |
| |
| GpPolicy * parPolicy = GpPolicyFetch(CurrentMemoryContext, |
| RelationGetRelid(par_rel)); |
| { |
| List *l1; |
| ListCell *lc; |
| int ii = 0; |
| bool bFixFirstATS = true; |
| bool bFirst_TemplateOnly = true; /* ignore dummy entry */ |
| int pby_templ_depth = 0; /* template partdepth */ |
| Oid skipTableRelid = InvalidOid; |
| List *attr_encodings = NIL; |
| |
| ct->base.partitionBy = (Node *)pBy; |
| |
| /* this parse_analyze expands the phony create of a partitioned table |
| * that we just build into the constituent commands we need to create |
| * the new part. (This will include some commands for the parent that |
| * we don't need, since the parent already exists.) |
| */ |
| |
| l1 = parse_analyze((Node *)ct, NULL, NULL, 0); |
| |
| /* |
| * Must reference ct->attr_encodings after parse_analyze() since that |
| * stage by change the attribute encodings via expansion. |
| */ |
| attr_encodings = ct->attr_encodings; |
| |
| /* |
| * Look for the first CreateStmt and generate a GrantStmt |
| * based on the RangeVar in it. |
| */ |
| foreach(lc, l1) |
| { |
| Node *s = lfirst(lc); |
| |
| /* skip the first one, it's the fake create table for the parent */ |
| if (lc == list_head(l1)) |
| continue; |
| |
| if (IsA(s, Query) && IsA(((Query *)s)->utilityStmt, CreateStmt)) |
| { |
| HeapTuple tuple; |
| Datum aclDatum; |
| bool isNull; |
| CreateStmt *t = (CreateStmt *)((Query *)s)->utilityStmt; |
| cqContext *classcqCtx; |
| |
| t->attr_encodings = copyObject(attr_encodings); |
| |
| classcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(RelationGetRelid(rel)))); |
| |
| tuple = caql_getnext(classcqCtx); |
| |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for relation %u", |
| RelationGetRelid(rel)); |
| |
| aclDatum = caql_getattr(classcqCtx, |
| Anum_pg_class_relacl, |
| &isNull); |
| if (!isNull) |
| { |
| List *cp = NIL; |
| int i, num; |
| Acl *acl; |
| AclItem *aidat; |
| |
| acl = DatumGetAclP(aclDatum); |
| |
| num = ACL_NUM(acl); |
| aidat = ACL_DAT(acl); |
| |
| for (i = 0; i < num; i++) |
| { |
| AclItem *aidata = &aidat[i]; |
| Datum d; |
| char *str; |
| |
| d = DirectFunctionCall1(aclitemout, |
| PointerGetDatum(aidata)); |
| str = DatumGetCString(d); |
| |
| cp = lappend(cp, makeString(str)); |
| |
| } |
| |
| if (list_length(cp)) |
| { |
| GrantStmt *gs = makeNode(GrantStmt); |
| List *pt; |
| |
| gs->is_grant = true; |
| gs->objtype = ACL_OBJECT_RELATION; |
| gs->cooked_privs = cp; |
| |
| gs->objects = list_make1(copyObject(t->base.relation)); |
| |
| pt = parse_analyze((Node *)gs, NULL, NULL, 0); |
| l1 = list_concat(l1, pt); |
| } |
| } |
| |
| caql_endscan(classcqCtx); |
| break; |
| } |
| } |
| |
| /* skip the first cell because the table already exists -- |
| * don't recreate it |
| */ |
| lc = list_head(l1); |
| |
| if (lc) |
| { |
| Node *s = lfirst(lc); |
| |
| /* MPP-10421: but save the relid of the skipped table, |
| * because we skip indexes associated with it... |
| */ |
| if (IsA(s, Query) && IsA(((Query *)s)->utilityStmt, CreateStmt)) |
| { |
| CreateStmt *t = (CreateStmt *)((Query *)s)->utilityStmt; |
| |
| skipTableRelid = RangeVarGetRelid(t->base.relation, true, false /*allowHcatalog*/); |
| } |
| } |
| |
| for_each_cell(lc, lnext(lc)) |
| { |
| Query *q = lfirst(lc); |
| |
| /* |
| * MPP-6379, MPP-10421: If the statement is an expanded |
| * index creation statement on the parent (or the "skipped |
| * table"), ignore it. We get into this situation when the |
| * parent has one or more indexes on it that our new |
| * partition is inheriting. |
| */ |
| if (IsA(q->utilityStmt, IndexStmt)) |
| { |
| IndexStmt *istmt = (IndexStmt *)q->utilityStmt; |
| Oid idxRelid = RangeVarGetRelid(istmt->relation, true, false /*allowHcatalog*/); |
| |
| if (idxRelid == RelationGetRelid(rel)) |
| continue; |
| |
| if (OidIsValid(skipTableRelid) && |
| (idxRelid == skipTableRelid)) |
| continue; |
| } |
| |
| /* XXX XXX: fix the first Alter Table Statement to have |
| * the correct maxpartno. Whoohoo!! |
| */ |
| if (bFixFirstATS && q && q->utilityStmt |
| && IsA(q->utilityStmt, AlterTableStmt)) |
| { |
| PartitionSpec *spec = NULL; |
| AlterTableStmt *ats; |
| AlterTableCmd *atc; |
| List *cmds; |
| |
| bFixFirstATS = false; |
| |
| ats = (AlterTableStmt *)q->utilityStmt; |
| Assert(IsA(ats, AlterTableStmt)); |
| |
| cmds = ats->cmds; |
| |
| Assert(cmds && (list_length(cmds) > 1)); |
| |
| atc = (AlterTableCmd *)lsecond(cmds); |
| |
| Assert(atc->def); |
| |
| pBy = (PartitionBy *)atc->def; |
| |
| Assert(IsA(pBy, PartitionBy)); |
| |
| spec = (PartitionSpec *)pBy->partSpec; |
| |
| if (spec) |
| { |
| List *l2 = spec->partElem; |
| PartitionElem *pel; |
| |
| if (l2 && list_length(l2)) |
| { |
| pel = (PartitionElem *)linitial(l2); |
| |
| pel->partno = maxpartno; |
| } |
| |
| } |
| |
| } /* end first alter table fixup */ |
| else if (IsA(q->utilityStmt, CreateStmt)) |
| { |
| /* propagate owner */ |
| ((CreateStmt *)q->utilityStmt)->ownerid = ownerid; |
| /* child partition should have the same bucket number with parent partition */ |
| if (parPolicy && ((CreateStmt *)q->utilityStmt)->policy |
| && ((CreateStmt *)q->utilityStmt)->policy->nattrs > 0 |
| && ((CreateStmt *)q->utilityStmt)->policy->ptype == |
| POLICYTYPE_PARTITIONED) { |
| ((CreateStmt *)q->utilityStmt)->policy->bucketnum = parPolicy->bucketnum; |
| } |
| } |
| |
| /* normal case - add partitions using CREATE statements |
| * that get dispatched to the segments |
| */ |
| if (!bSetTemplate) |
| ProcessUtility(q->utilityStmt, |
| synthetic_sql, |
| NULL, |
| false, /* not top level */ |
| dest, |
| NULL); |
| else |
| { /* setting subpartition template only */ |
| |
| /* find all the alter table statements that contain |
| * partaddinternal, and extract the definitions. Only |
| * build the catalog entries for subpartition |
| * templates, not "real" table entries. |
| */ |
| if (q && q->utilityStmt |
| && IsA(q->utilityStmt, AlterTableStmt)) |
| { |
| AlterTableStmt *at2 = (AlterTableStmt *)q->utilityStmt; |
| List *l2 = at2->cmds; |
| ListCell *lc2; |
| |
| foreach(lc2, l2) |
| { |
| AlterTableCmd *ac2 = (AlterTableCmd *) lfirst(lc2); |
| |
| if (ac2->subtype == AT_PartAddInternal) |
| { |
| PartitionBy *templ_pby = |
| (PartitionBy *)ac2->def; |
| |
| Assert(IsA(templ_pby, PartitionBy)); |
| |
| /* skip the first one because it's the |
| * fake parent partition definition for |
| * the subpartition template entries |
| */ |
| |
| if (bFirst_TemplateOnly) |
| { |
| bFirst_TemplateOnly = false; |
| |
| /* MPP-5992: only set one level of |
| * templates -- we might have |
| * templates for subpartitions of the |
| * subpartitions, which would add |
| * duplicate templates into the table. |
| * Only add templates of the specified |
| * depth and skip deeper template |
| * definitions. |
| */ |
| pby_templ_depth = templ_pby->partDepth + 1; |
| |
| } |
| else |
| { |
| if (templ_pby->partDepth == pby_templ_depth) |
| add_part_to_catalog( |
| RelationGetRelid(rel), |
| (PartitionBy *)ac2->def, |
| true); |
| } |
| |
| } |
| } /* end foreach lc2 l2 */ |
| } |
| } /* end else setting subpartition templates only */ |
| |
| ii++; |
| } /* end for each cell */ |
| |
| } |
| if(parPolicy) |
| pfree(parPolicy); |
| |
| if (par_prule && par_prule->topRule) |
| heap_close(par_rel, NoLock); |
| |
| return pSubSpec; |
| } /* end atpxPartAddList */ |
| |
| |
| List * |
| atpxDropList(Relation rel, PartitionNode *pNode) |
| { |
| List *l1 = NIL; |
| ListCell *lc; |
| |
| if (!pNode) |
| return l1; |
| |
| /* add the child lists first */ |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| List *l2 = NIL; |
| |
| if (rule->children) |
| l2 = atpxDropList(rel, rule->children); |
| else |
| l2 = NIL; |
| |
| if (l2) |
| { |
| if (l1) |
| l1 = list_concat(l1, l2); |
| else |
| l1 = l2; |
| } |
| } |
| |
| /* and the default partition */ |
| if (pNode->default_part) |
| { |
| PartitionRule *rule = pNode->default_part; |
| List *l2 = NIL; |
| |
| if (rule->children) |
| l2 = atpxDropList(rel, rule->children); |
| else |
| l2 = NIL; |
| |
| if (l2) |
| { |
| if (l1) |
| l1 = list_concat(l1, l2); |
| else |
| l1 = l2; |
| } |
| } |
| |
| /* add entries for rules at current level */ |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| char *prelname; |
| char *nspname; |
| Relation rel; |
| |
| rel = heap_open(rule->parchildrelid, AccessShareLock); |
| prelname = pstrdup(RelationGetRelationName(rel)); |
| nspname = pstrdup(get_namespace_name(RelationGetNamespace(rel))); |
| heap_close(rel, NoLock); |
| |
| if (l1) |
| l1 = lappend(l1, list_make2(makeString(nspname), |
| makeString(prelname))); |
| else |
| l1 = list_make1(list_make2(makeString(nspname), |
| makeString(prelname))); |
| } |
| |
| /* and the default partition */ |
| if (pNode->default_part) |
| { |
| PartitionRule *rule = pNode->default_part; |
| char *prelname; |
| char *nspname; |
| Relation rel; |
| |
| rel = heap_open(rule->parchildrelid, AccessShareLock); |
| prelname = pstrdup(RelationGetRelationName(rel)); |
| nspname = pstrdup(get_namespace_name(RelationGetNamespace(rel))); |
| heap_close(rel, NoLock); |
| |
| if (l1) |
| l1 = lappend(l1, list_make2(makeString(nspname), |
| makeString(prelname))); |
| else |
| l1 = list_make1(list_make2(makeString(nspname), |
| makeString(prelname))); |
| } |
| |
| return l1; |
| } /* end atpxDropList */ |
| |
| |
| void |
| exchange_part_rule(Oid oldrelid, Oid newrelid) |
| { |
| HeapTuple tuple; |
| Relation catalogRelation; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| catalogRelation = heap_open(PartitionRuleRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), catalogRelation); |
| |
| tuple = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE parchildrelid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(oldrelid))); |
| |
| if (HeapTupleIsValid(tuple)) |
| { |
| ((Form_pg_partition_rule)GETSTRUCT(tuple))->parchildrelid = newrelid; |
| |
| caql_update_current(pcqCtx, tuple); |
| /* and Update indexes (implicit) */ |
| |
| heap_freetuple(tuple); |
| } |
| |
| heap_close(catalogRelation, NoLock); |
| } |
| |
| void |
| exchange_permissions(Oid oldrelid, Oid newrelid) |
| { |
| HeapTuple oldtuple; |
| HeapTuple newtuple; |
| Datum save; |
| bool saveisnull; |
| Datum values[Natts_pg_class]; |
| bool nulls[Natts_pg_class]; |
| bool replaces[Natts_pg_class]; |
| HeapTuple replace_tuple; |
| bool isnull; |
| Relation rel = heap_open(RelationRelationId, RowExclusiveLock); |
| cqContext oldcqc; |
| cqContext newcqc; |
| cqContext *oldpcqCtx; |
| cqContext *newpcqCtx; |
| |
| oldpcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&oldcqc), rel), |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(oldrelid))); |
| |
| oldtuple = caql_getnext(oldpcqCtx); |
| |
| if (!HeapTupleIsValid(oldtuple)) |
| elog(ERROR, "cache lookup failed for relation %u", oldrelid); |
| |
| save = caql_getattr(oldpcqCtx, |
| Anum_pg_class_relacl, |
| &saveisnull); |
| |
| newpcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&newcqc), rel), |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(newrelid))); |
| |
| newtuple = caql_getnext(newpcqCtx); |
| |
| if (!HeapTupleIsValid(newtuple)) |
| elog(ERROR, "cache lookup failed for relation %u", newrelid); |
| |
| /* finished building new ACL value, now insert it */ |
| MemSet(values, 0, sizeof(values)); |
| MemSet(nulls, false, sizeof(nulls)); |
| MemSet(replaces, false, sizeof(replaces)); |
| |
| replaces[Anum_pg_class_relacl - 1] = true; |
| values[Anum_pg_class_relacl - 1] = caql_getattr(newpcqCtx, |
| Anum_pg_class_relacl, |
| &isnull); |
| if (isnull) |
| nulls[Anum_pg_class_relacl - 1] = true; |
| |
| replace_tuple = caql_modify_current(oldpcqCtx, |
| values, nulls, replaces); |
| |
| caql_update_current(oldpcqCtx, replace_tuple); |
| /* and Update indexes (implicit) */ |
| |
| /* XXX: Update the shared dependency ACL info */ |
| |
| /* finished building new ACL value, now insert it */ |
| MemSet(values, 0, sizeof(values)); |
| MemSet(nulls, false, sizeof(nulls)); |
| MemSet(replaces, false, sizeof(replaces)); |
| |
| replaces[Anum_pg_class_relacl - 1] = true; |
| values[Anum_pg_class_relacl - 1] = save; |
| |
| if (saveisnull) |
| nulls[Anum_pg_class_relacl - 1] = true; |
| |
| replace_tuple = caql_modify_current(newpcqCtx, |
| values, nulls, replaces); |
| |
| caql_update_current(newpcqCtx, replace_tuple); |
| /* and Update indexes (implicit) */ |
| |
| /* update shared dependency */ |
| |
| caql_endscan(oldpcqCtx); |
| caql_endscan(newpcqCtx); |
| heap_close(rel, NoLock); |
| } |
| |
| |
| bool |
| atpxModifyListOverlap (Relation rel, |
| AlterPartitionId *pid, |
| PgPartRule *prule, |
| PartitionElem *pelem, |
| bool bAdd) |
| { |
| if (prule->pNode->default_part && bAdd) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("cannot MODIFY %s partition%s for " |
| "relation \"%s\" to ADD values -- would " |
| "overlap DEFAULT partition \"%s\"", |
| "LIST", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| prule->pNode->default_part->parname), |
| errhint("need to SPLIT partition \"%s\"", |
| prule->pNode->default_part->parname), |
| errOmitLocation(true))); |
| |
| { |
| ListCell *lc; |
| PartitionValuesSpec *pVSpec; |
| AlterPartitionId pid2; |
| PartitionNode *pNode = prule->pNode; |
| int partno = 0; |
| CreateStmtContext cxt; |
| |
| MemSet(&cxt, 0, sizeof(cxt)); |
| |
| Assert (IsA(pelem->boundSpec, PartitionValuesSpec)); |
| |
| MemSet(&pid2, 0, sizeof(AlterPartitionId)); |
| |
| /* this function does transformExpr on the boundary specs */ |
| partno = atpxPart_validate_spec(makeNode(PartitionBy), |
| &cxt, |
| rel, |
| NULL, /* CreateStmt */ |
| pelem, |
| pNode, |
| (pid->idtype == AT_AP_IDName) ? |
| pid->partiddef : NULL, |
| false, /* isDefault */ |
| PARTTYP_LIST, /* part_type */ |
| prule->partIdStr); |
| |
| pVSpec = (PartitionValuesSpec *)pelem->boundSpec; |
| |
| foreach(lc, pVSpec->partValues) |
| { |
| List *vals = lfirst(lc); |
| PgPartRule *prule2 = NULL; |
| |
| pid2.idtype = AT_AP_IDValue; |
| pid2.partiddef = (Node *)vals; |
| pid2.location = -1; |
| |
| prule2 = get_part_rule(rel, &pid2, false, false, |
| CurrentMemoryContext, NULL, false); |
| |
| if (bAdd) |
| { |
| /* if ADDing a value, should not match */ |
| |
| if (prule2) |
| { |
| if (prule2->topRuleRank != prule->topRuleRank) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot MODIFY LIST partition%s for " |
| "relation \"%s\" -- " |
| "would overlap " |
| "existing partition%s", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| prule2->isName ? |
| prule2->partIdStr : ""), |
| errOmitLocation(true))); |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot MODIFY LIST partition%s for " |
| "relation \"%s\" -- " |
| "ADD value has duplicate in " |
| "existing partition", |
| prule->partIdStr, |
| RelationGetRelationName(rel)), |
| errOmitLocation(true))); |
| } |
| } |
| else /* DROP values */ |
| { |
| /* if DROPping a value, it should only be in the |
| * specified partition |
| */ |
| |
| if (!prule2) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot MODIFY LIST partition%s for " |
| "relation \"%s\" -- DROP value not found", |
| prule->partIdStr, |
| RelationGetRelationName(rel)), |
| errOmitLocation(true))); |
| } |
| |
| if (prule2 && (prule2->topRuleRank != prule->topRuleRank)) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot MODIFY LIST partition%s for " |
| "relation \"%s\" -- " |
| "found DROP value in%s partition%s", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| prule2->isName ? "" : "other", |
| prule2->isName ? prule2->partIdStr : ""), |
| errOmitLocation(true))); |
| } |
| } |
| |
| } /* end foreach */ |
| } |
| |
| return false; |
| } /* end atpxModifyListOverlap */ |
| |
| bool |
| atpxModifyRangeOverlap (Relation rel, |
| AlterPartitionId *pid, |
| PgPartRule *prule, |
| PartitionElem *pelem) |
| { |
| PgPartRule *prule2 = NULL; |
| AlterPartitionId pid2; |
| PartitionNode *pNode = prule->pNode; |
| bool bCheckStart = true; |
| PartitionBoundSpec *pbs = NULL; |
| ParseState *pstate = NULL; |
| bool bOverlap = false; |
| bool *isnull; |
| TupleDesc tupledesc = RelationGetDescr(rel); |
| Datum *d_start = NULL; |
| Datum *d_end = NULL; |
| Node *pRangeValList = NULL; |
| int ii; |
| |
| Assert (IsA(pelem->boundSpec, PartitionBoundSpec)); |
| |
| pbs = (PartitionBoundSpec *)pelem->boundSpec; |
| |
| for (ii = 0; ii < 2 ; ii++) |
| { |
| PartitionRangeItem *ri; |
| |
| if (bCheckStart) /* check START first, then END */ |
| { |
| if (!(pbs->partStart)) |
| { |
| bCheckStart = false; |
| |
| if (prule->topRule->parrangestart) |
| { |
| PartitionRangeItem *ri = makeNode(PartitionRangeItem); |
| |
| ri->location = -1; |
| |
| ri->partRangeVal = |
| copyObject(prule->topRule->parrangestart); |
| |
| ri->partedge = prule->topRule->parrangestartincl ? |
| PART_EDGE_INCLUSIVE : |
| PART_EDGE_EXCLUSIVE; |
| |
| /* no start, so use current start */ |
| pbs->partStart = (Node *)ri; |
| } |
| |
| continue; |
| } |
| ri = (PartitionRangeItem *)pbs->partStart; |
| } |
| else |
| { |
| /* no END, so we are done */ |
| if (!(pbs->partEnd)) |
| { |
| |
| if (prule->topRule->parrangeend) |
| { |
| PartitionRangeItem *ri = makeNode(PartitionRangeItem); |
| |
| ri->location = -1; |
| |
| ri->partRangeVal = copyObject(prule->topRule->parrangeend); |
| |
| ri->partedge = prule->topRule->parrangeendincl ? |
| PART_EDGE_INCLUSIVE : |
| PART_EDGE_EXCLUSIVE; |
| |
| /* no end, so use current end */ |
| pbs->partEnd = (Node *)ri; |
| } |
| |
| break; |
| } |
| ri = (PartitionRangeItem *)pbs->partEnd; |
| } |
| |
| MemSet(&pid2, 0, sizeof(AlterPartitionId)); |
| |
| pid2.idtype = AT_AP_IDValue; |
| pstate = make_parsestate(NULL); |
| pRangeValList = (Node *) copyObject(ri->partRangeVal); |
| pRangeValList = (Node *) |
| transformExpressionList(pstate, (List *)pRangeValList); |
| free_parsestate(&pstate); |
| pid2.partiddef = pRangeValList; |
| pid2.location = -1; |
| |
| prule2 = get_part_rule(rel, &pid2, false, false, |
| CurrentMemoryContext, NULL, false); |
| |
| if (!prule2) |
| { |
| /* no rules matched -- this is ok as long as no |
| * default partition |
| */ |
| if (prule->pNode->default_part) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("cannot MODIFY %s partition%s for " |
| "relation \"%s\" to extend range -- would " |
| "overlap DEFAULT partition \"%s\"", |
| "RANGE", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| prule->pNode->default_part->parname), |
| errhint("need to SPLIT partition \"%s\"", |
| prule->pNode->default_part->parname), |
| errOmitLocation(true))); |
| |
| /* if only 1 partition no further check needed */ |
| if (1 == list_length(pNode->rules)) |
| continue; |
| |
| if (bCheckStart) |
| { |
| bool bstat; |
| PartitionRule *a_rule; |
| |
| /* check for adjacent partition */ |
| if (1 == prule->topRuleRank) |
| continue; /* no previous, so changing start is ok */ |
| |
| MemSet(&pid2, 0, sizeof(AlterPartitionId)); |
| |
| pid2.idtype = AT_AP_IDRank; |
| pid2.partiddef = (Node *)makeInteger(prule->topRuleRank - 1); |
| pid2.location = -1; |
| |
| prule2 = get_part_rule(rel, &pid2, false, false, |
| CurrentMemoryContext, NULL, false); |
| |
| Assert(prule2); |
| |
| a_rule = prule2->topRule; |
| |
| /* just check against end of adjacent partition */ |
| d_start = |
| magic_expr_to_datum(rel, pNode, |
| pRangeValList, &isnull); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| if (bstat) |
| { |
| /* end > new start - overlap */ |
| bOverlap = true; |
| break; |
| } |
| |
| /* could be the case that new start == end of |
| * previous. This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE. |
| */ |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| |
| if (bstat) |
| { |
| if (a_rule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap */ |
| bOverlap = true; |
| break; |
| } |
| } |
| } |
| else /* check the end */ |
| { |
| bool bstat; |
| PartitionRule *a_rule; |
| |
| /* check for adjacent partition */ |
| if (list_length(pNode->rules) == prule->topRuleRank) |
| continue; /* no next, so changing end is ok */ |
| |
| MemSet(&pid2, 0, sizeof(AlterPartitionId)); |
| |
| pid2.idtype = AT_AP_IDRank; |
| pid2.partiddef = (Node *)makeInteger(prule->topRuleRank + 1); |
| pid2.location = -1; |
| |
| prule2 = get_part_rule(rel, &pid2, false, false, |
| CurrentMemoryContext, NULL, false); |
| |
| Assert(prule2); |
| |
| a_rule = prule2->topRule; |
| |
| /* just check against start of adjacent partition */ |
| d_end = |
| magic_expr_to_datum(rel, pNode, |
| pRangeValList, &isnull); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| if (bstat) |
| { |
| /* start < new end - overlap */ |
| bOverlap = true; |
| break; |
| } |
| |
| /* could be the case that new end == start of |
| * next. This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE. |
| */ |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| |
| if (bstat) |
| { |
| if (a_rule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap */ |
| bOverlap = true; |
| break; |
| } |
| } |
| } /* end else check the end */ |
| |
| } |
| else |
| { |
| /* matched a rule - definitely a problem if the range was |
| * inclusive |
| */ |
| if (prule2->topRuleRank != prule->topRuleRank) |
| { |
| if (0 == prule2->topRuleRank) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("cannot MODIFY %s partition%s for " |
| "relation \"%s\" to extend range -- would " |
| "overlap DEFAULT partition \"%s\"", |
| "RANGE", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| prule->pNode->default_part->parname), |
| errhint("need to SPLIT partition \"%s\"", |
| prule->pNode->default_part->parname), |
| errOmitLocation(true))); |
| |
| |
| if (ri->partedge == PART_EDGE_INCLUSIVE) |
| { |
| bOverlap = true; |
| break; |
| } |
| |
| /* range was exclusive -- need to do some checking */ |
| if (bCheckStart) |
| { |
| bool bstat; |
| PartitionRule *a_rule = prule2->topRule; |
| |
| /* check for adjacent partition */ |
| if ((prule->topRuleRank - 1) != prule2->topRuleRank) |
| { |
| bOverlap = true; |
| break; |
| } |
| |
| /* just check against end of adjacent partition */ |
| d_start = |
| magic_expr_to_datum(rel, pNode, |
| pid2.partiddef, &isnull); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| ">", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc); |
| if (bstat) |
| { |
| /* end > new start - overlap */ |
| bOverlap = true; |
| break; |
| } |
| |
| /* Must be the case that new start == end of |
| * a_rule (because if the end < new start then how |
| * could we find it in the interval for prule ?) |
| * This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE -> New partition does not |
| * overlap. |
| */ |
| |
| Assert (compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangeend, |
| d_start, isnull, tupledesc)); |
| |
| if (a_rule->parrangeendincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap |
| */ |
| bOverlap = true; |
| break; |
| } |
| } |
| else /* check the end */ |
| { |
| bool bstat; |
| PartitionRule *a_rule = prule2->topRule; |
| |
| /* check for adjacent partition */ |
| if ((prule->topRuleRank + 1) != prule2->topRuleRank) |
| { |
| bOverlap = true; |
| break; |
| } |
| |
| /* just check against start of adjacent partition */ |
| d_end = |
| magic_expr_to_datum(rel, pNode, |
| pid2.partiddef, &isnull); |
| |
| bstat = |
| compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "<", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc); |
| if (bstat) |
| { |
| /* start < new end - overlap */ |
| bOverlap = true; |
| break; |
| } |
| |
| /* Must be the case that new end = start of |
| * a_rule (because if the start > new end then how |
| * could we find it in the interval for prule ?) |
| * This is ok if they have opposite |
| * INCLUSIVE/EXCLUSIVE -> New partition does not |
| * overlap. |
| */ |
| |
| Assert (compare_partn_opfuncid(pNode, |
| "pg_catalog", |
| "=", |
| (List *)a_rule->parrangestart, |
| d_end, isnull, tupledesc)); |
| |
| if (a_rule->parrangestartincl == |
| (ri->partedge == PART_EDGE_INCLUSIVE)) |
| { |
| /* start and end must be of opposite |
| * types, else they overlap |
| */ |
| bOverlap = true; |
| break; |
| } |
| } /* end else check the end */ |
| } /* end if (prule2->topRuleRank != prule->topRuleRank) */ |
| } |
| |
| /* if checked START, then check END. If checked END, then done */ |
| if (!bCheckStart) |
| break; |
| if (bCheckStart) |
| bCheckStart = false; |
| } /* end for */ |
| |
| if (bOverlap) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("cannot MODIFY RANGE partition%s for " |
| "relation \"%s\" -- " |
| "would overlap " |
| "existing partition%s", |
| prule->partIdStr, |
| RelationGetRelationName(rel), |
| (prule2 && prule2->isName) ? |
| prule2->partIdStr : ""), |
| errOmitLocation(true))); |
| |
| { |
| int partno = 0; |
| CreateStmtContext cxt; |
| |
| MemSet(&cxt, 0, sizeof(cxt)); |
| |
| /* this function does transformExpr on the boundary specs */ |
| partno = atpxPart_validate_spec(makeNode(PartitionBy), |
| &cxt, |
| rel, |
| NULL, /* CreateStmt */ |
| pelem, |
| pNode, |
| (pid->idtype == AT_AP_IDName) ? |
| pid->partiddef : NULL, |
| false, /* isDefault */ |
| PARTTYP_RANGE, /* part_type */ |
| prule->partIdStr); |
| } |
| |
| |
| return false; |
| } /* end atpxModifyRangeOverlap */ |
| |
| static void atpxSkipper(PartitionNode *pNode, int *skipped) |
| { |
| ListCell *lc; |
| |
| if (!pNode) return; |
| |
| /* add entries for rules at current level */ |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| |
| if (skipped) *skipped += 1; |
| |
| if (rule->children) |
| atpxSkipper(rule->children, skipped); |
| } /* end foreach */ |
| |
| /* and the default partition */ |
| if (pNode->default_part) |
| { |
| PartitionRule *rule = pNode->default_part; |
| |
| if (skipped) *skipped += 1; |
| |
| if (rule->children) |
| atpxSkipper(rule->children, skipped); |
| } |
| } /* end atpxSkipper */ |
| |
| static List * |
| build_rename_part_recurse(PartitionRule *rule, const char *old_parentname, |
| const char *new_parentname, |
| int *skipped) |
| { |
| |
| RangeVar *rv; |
| Relation rel; |
| char *relname = NULL; |
| char newRelNameBuf[(NAMEDATALEN*2)]; |
| List *l1 = NIL; |
| |
| rel = heap_open(rule->parchildrelid, AccessShareLock); |
| |
| relname = pstrdup(RelationGetRelationName(rel)); |
| |
| rv = makeRangeVar(NULL /*catalogname*/, get_namespace_name(RelationGetNamespace(rel)), |
| relname, -1); |
| |
| /* unlock, because we have a lock on the master */ |
| heap_close(rel, AccessShareLock); |
| |
| /* |
| * The child name should contain the old parent name as a |
| * prefix - check the length and compare to make sure. |
| * |
| * To build the new child name, just use the new name as a |
| * prefix, and use the remainder of the child name (the part |
| * after the old parent name prefix) as the suffix. |
| */ |
| if (strlen(old_parentname) > strlen(relname)) |
| { |
| if (skipped) |
| *skipped += 1; |
| |
| atpxSkipper(rule->children, skipped); |
| } |
| else |
| { |
| if (0 != (strncmp(old_parentname, relname, strlen(old_parentname)))) |
| { |
| if (skipped) |
| *skipped += 1; |
| atpxSkipper(rule->children, skipped); |
| } |
| else |
| { |
| snprintf(newRelNameBuf, sizeof(newRelNameBuf), "%s%s", |
| new_parentname, relname + strlen(old_parentname)); |
| |
| if (strlen(newRelNameBuf) > NAMEDATALEN) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| errmsg("relation name \"%s\" for child partition " |
| "is too long", |
| newRelNameBuf))); |
| |
| l1 = lappend(l1, list_make2(rv, pstrdup(newRelNameBuf))); |
| |
| /* add the child lists next (not first) */ |
| { |
| List *l2 = NIL; |
| |
| if (rule->children) |
| l2 = atpxRenameList(rule->children, |
| relname, newRelNameBuf, skipped); |
| |
| if (l2) |
| l1 = list_concat(l1, l2); |
| } |
| } |
| } |
| return l1; |
| } |
| |
| List * |
| atpxRenameList(PartitionNode *pNode, |
| char *old_parentname, const char *new_parentname, int *skipped) |
| { |
| List *l1 = NIL; |
| ListCell *lc; |
| |
| if (!pNode) |
| return l1; |
| |
| /* add entries for rules at current level */ |
| foreach(lc, pNode->rules) |
| { |
| PartitionRule *rule = lfirst(lc); |
| |
| l1 = list_concat(l1, |
| build_rename_part_recurse(rule, |
| old_parentname, |
| new_parentname, |
| skipped)); |
| } /* end foreach */ |
| |
| /* and the default partition */ |
| if (pNode->default_part) |
| { |
| PartitionRule *rule = pNode->default_part; |
| |
| l1 = list_concat(l1, |
| build_rename_part_recurse(rule, |
| old_parentname, |
| new_parentname, |
| skipped)); |
| } |
| |
| return l1; |
| } /* end atpxRenameList */ |
| |
| |
| static Oid |
| get_opfuncid_by_opname(List *opname, Oid lhsid, Oid rhsid) |
| { |
| Oid opfuncid; |
| Operator op; |
| |
| op = oper(NULL, opname, lhsid, rhsid, false, -1); |
| |
| if (op == NULL) /* should not fail */ |
| elog(ERROR, "could not find operator"); |
| |
| opfuncid = ((Form_pg_operator)GETSTRUCT(op))->oprcode; |
| |
| ReleaseOperator(op); |
| return opfuncid; |
| } |
| |
| |
| /* Construct the PgPartRule for a branch of a partitioning hierarchy. |
| * |
| * rel - the partitioned relation (top-level) |
| * cmd - an AlterTableCmd, possibly nested in type AT_PartAlter AlterTableCmds |
| * identifying a subset of the parts of the partitioned relation. |
| */ |
| static PgPartRule * |
| get_pprule_from_ATC(Relation rel, AlterTableCmd *cmd) |
| { |
| List *pids = NIL; /* of AlterPartitionId */ |
| AlterPartitionId *pid = NULL; |
| PgPartRule *pprule = NULL; |
| AlterPartitionId *work_partid = NULL; |
| |
| AlterTableCmd *atc = cmd; |
| |
| |
| /* Get list of enclosing ALTER PARTITION ids. */ |
| while ( atc->subtype == AT_PartAlter ) |
| { |
| AlterPartitionCmd *apc = (AlterPartitionCmd*)atc->def; |
| |
| pid = (AlterPartitionId*)apc->partid; |
| Insist(IsA(pid, AlterPartitionId)); |
| |
| atc = (AlterTableCmd*)apc->arg1; |
| Insist(IsA(atc, AlterTableCmd)); |
| |
| pids = lappend(pids, pid); |
| } |
| |
| /* The effective ALTER TABLE command is in atc. |
| * The pids list (of AlterPartitionId nodes) represents the path to |
| * top partitioning branch of rel. Since we are only called for |
| * branches and leaves (never the root) of the partition, the pid |
| * list should not empty. |
| * |
| * Use the AlterPartitionId interpretter, get_part_rule, to do |
| * the interpretation. |
| */ |
| Insist( list_length(pids) > 0 ); |
| |
| work_partid = makeNode(AlterPartitionId); |
| |
| work_partid->idtype = AT_AP_IDList; |
| work_partid->partiddef = (Node*)pids; |
| work_partid->location = -1; |
| |
| pprule = get_part_rule(rel, |
| work_partid, |
| true, true, /* parts must exist */ |
| CurrentMemoryContext, |
| NULL, /* no implicit results */ |
| false /* no template rules */ |
| ); |
| |
| return pprule; |
| } |
| |
| /* Return the pg_class OIDs of the relations representing the parts of |
| * a partitioned table designated by the given AlterTable command. |
| * |
| * rel - the partitioned relation (top-level) |
| * cmd - an AlterTableCmd, possibly nested in type AT_PartAlter AlterTableCmds |
| * identifying a subset of the parts of the partitioned relation. |
| */ |
| List * |
| basic_AT_oids(Relation rel, AlterTableCmd *cmd) |
| { |
| PgPartRule *pprule = get_pprule_from_ATC(rel, cmd); |
| |
| if ( ! pprule ) |
| return NIL; |
| |
| return all_prule_relids(pprule->topRule); |
| } |
| |
| /* |
| * Return the basic AlterTableCmd found by peeling off intervening layers of |
| * ALTER PARTITION from the given AlterTableCmd. |
| */ |
| AlterTableCmd *basic_AT_cmd(AlterTableCmd *cmd) |
| { |
| while ( cmd->subtype == AT_PartAlter ) |
| { |
| AlterPartitionCmd *apc = (AlterPartitionCmd*)cmd->def; |
| Insist(IsA(apc, AlterPartitionCmd)); |
| cmd = (AlterTableCmd*)apc->arg1; |
| Insist(IsA(cmd, AlterTableCmd)); |
| } |
| return cmd; |
| } |
| |
| |
| /* Determine whether we can implement a requested distribution on a part of |
| * the specified partitioned table. |
| * |
| * In 3.3 |
| * DISTRIBUTED RANDOMLY or distributed just like the whole partitioned |
| * table is implementable. Anything else is not. |
| * |
| * rel Pointer to cache entry for the whole partitioned table |
| * dist_cnames List of column names proposed for distribution some part |
| */ |
| bool can_implement_dist_on_part(Relation rel, List *dist_cnames) |
| { |
| ListCell *lc; |
| int i; |
| |
| if (Gp_role != GP_ROLE_DISPATCH) |
| { |
| ereport(DEBUG1, |
| (errmsg("requesting redistribution outside dispatch - returning no"))); |
| return false; |
| } |
| |
| /* Random is okay. It is represented by a list of one empty list. */ |
| if ( list_length(dist_cnames) == 1 && linitial(dist_cnames) == NIL ) |
| return true; |
| |
| /* Require an exact match to the policy of the parent. */ |
| if ( list_length(dist_cnames) != rel->rd_cdbpolicy->nattrs ) |
| return false; |
| |
| i = 0; |
| foreach(lc, dist_cnames) |
| { |
| AttrNumber attnum; |
| char *cname; |
| HeapTuple tuple; |
| Node *item = lfirst(lc); |
| bool ok = false; |
| cqContext *pcqCtx; |
| |
| if ( !(item && IsA(item, String)) ) |
| return false; |
| |
| cname = strVal((Value *)item); |
| pcqCtx = caql_getattname_scan(NULL, RelationGetRelid(rel), cname); |
| tuple = caql_get_current(pcqCtx); |
| |
| if (!HeapTupleIsValid(tuple)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_COLUMN), |
| errmsg("column \"%s\" of relation \"%s\" does not exist", |
| cname, |
| RelationGetRelationName(rel)), |
| errOmitLocation(true))); |
| |
| attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; |
| ok = attnum == rel->rd_cdbpolicy->attrs[i++]; |
| caql_endscan(pcqCtx); |
| |
| if ( ! ok ) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| |
| /* Test whether we can exchange newrel into oldrel's space within the |
| * partitioning hierarchy of rel as far as the table schema is concerned. |
| * (Does not, e.g., look into constraint agreement, etc.) |
| * |
| * If throw is true, throw an appropriate error in case the answer is |
| * "no, can't exchange". If throw is false, just return the answer |
| * quietly. |
| */ |
| bool |
| is_exchangeable(Relation rel, Relation oldrel, Relation newrel, bool throw) |
| { |
| AttrMap *map_new = NULL; |
| AttrMap *map_old = NULL; |
| bool congruent = TRUE; |
| |
| /* Both parts must be relations. */ |
| if (!(oldrel->rd_rel->relkind == RELKIND_RELATION || |
| newrel->rd_rel->relkind == RELKIND_RELATION)) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot exchange relation " |
| "which is not a table"))); |
| } |
| |
| /* Attributes of the existing part (oldrel) must be compatible with the |
| * partitioned table as a whole. This might be an assertion, but we don't |
| * want this case to pass in a production build, so we use an internal |
| * error. |
| */ |
| if (congruent && ! map_part_attrs(rel, oldrel, &map_old, FALSE) ) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| elog(ERROR, "existing part \"%s\" not congruent with" |
| "partitioned table \"%s\"", |
| RelationGetRelationName(oldrel), |
| RelationGetRelationName(rel)); |
| } |
| |
| /* From here on we need to be careful to free the maps. */ |
| |
| /* Attributes of new part must be compatible with the partitioned table. |
| * (We assume that the attributes of the old part are compatible.) |
| */ |
| if ( congruent && ! map_part_attrs(rel, newrel, &map_new, throw) ) |
| congruent = FALSE; |
| |
| /* Both parts must have the same owner. */ |
| if ( congruent && oldrel->rd_rel->relowner != newrel->rd_rel->relowner) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("owner of \"%s\" must be the same as that " |
| "of \"%s\"", |
| RelationGetRelationName(newrel), |
| RelationGetRelationName(rel)), |
| errOmitLocation(true))); |
| } |
| |
| /* Both part tables must have the same "WITH OID"s setting */ |
| if (congruent && oldrel->rd_rel->relhasoids != newrel->rd_rel->relhasoids) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("\"%s\" and \"%s\" must have same OIDs setting", |
| RelationGetRelationName(rel), |
| RelationGetRelationName(newrel)), |
| errOmitLocation(true))); |
| } |
| |
| /* The new part table must not be involved in inheritance. */ |
| if ( congruent && has_subclass_fast(RelationGetRelid(newrel))) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot EXCHANGE table \"%s\" as it has " |
| "child table(s)", |
| RelationGetRelationName(newrel)), |
| errOmitLocation(true))); |
| } |
| |
| if (congruent && relation_has_supers(RelationGetRelid(newrel))) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot exchange table \"%s\" as it " |
| "inherits other table(s)", |
| RelationGetRelationName(newrel)), |
| errOmitLocation(true))); |
| } |
| |
| /* The new part table must not have rules on it. */ |
| if ( congruent && ( newrel->rd_rules || oldrel->rd_rules ) ) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot exchange table which has rules " |
| "defined on it"), |
| errOmitLocation(true))); |
| } |
| |
| /* The distribution policies of the existing part (oldpart) and the |
| * candidate part (newpart) must match that of the whole partitioned |
| * table. However, we can only check this where the policy table is |
| * populated, i.e., on the entry database. Note checking the policy |
| * of the existing part is defensive. It SHOULD match. |
| */ |
| if (congruent && Gp_role == GP_ROLE_DISPATCH) |
| { |
| GpPolicy *parpol = rel->rd_cdbpolicy; |
| GpPolicy *oldpol = oldrel->rd_cdbpolicy; |
| GpPolicy *newpol = newrel->rd_cdbpolicy; |
| GpPolicy *adjpol = NULL; |
| |
| if ( map_old != NULL ) |
| { |
| int i; |
| AttrNumber remapped_parent_attr = 0; |
| |
| for ( i = 0; i < parpol->nattrs; i++ ) |
| { |
| remapped_parent_attr = attrMap(map_old, parpol->attrs[i]); |
| |
| if ( ! (parpol->attrs[i] > 0 /* assert parent live */ |
| && oldpol->attrs[i] > 0 /* assert old part live */ |
| && remapped_parent_attr == oldpol->attrs[i] /* assert match */ |
| )) |
| elog(ERROR, |
| "discrepancy in partitioning policy of \"%s\"", |
| RelationGetRelationName(rel)); |
| } |
| } |
| else |
| { |
| if (! GpPolicyEqual(parpol, oldpol) ) |
| elog(ERROR, |
| "discrepancy in partitioning policy of \"%s\"", |
| RelationGetRelationName(rel)); |
| } |
| |
| if ( map_new != NULL ) |
| { |
| int i; |
| adjpol = GpPolicyCopy(CurrentMemoryContext, parpol); |
| |
| for ( i = 0; i < adjpol->nattrs; i++ ) |
| { |
| adjpol->attrs[i] = attrMap(map_new, parpol->attrs[i]); |
| Assert(newpol->attrs[i] > 0); /* check new part */ |
| } |
| } |
| else |
| { |
| adjpol = parpol; |
| } |
| |
| if (! GpPolicyEqual(adjpol, newpol) ) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("distribution policy for \"%s\" " |
| "must be the same as that for \"%s\"", |
| RelationGetRelationName(newrel), |
| RelationGetRelationName(rel)), |
| errOmitLocation(true))); |
| } |
| else if (false && memcmp(oldpol->attrs, newpol->attrs, |
| sizeof(AttrNumber) * adjpol->nattrs)) |
| { |
| congruent = FALSE; |
| if ( throw ) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("distribution policy matches but implementation lags"), |
| errOmitLocation(true))); |
| } |
| } |
| |
| if ( map_old != NULL ) pfree(map_old); |
| if ( map_new != NULL ) pfree(map_new); |
| |
| return congruent; |
| } |
| |
| |
| /* |
| * Apply the constraint represented by the argument pg_constraint tuple |
| * remapped through the argument attribute map to the candidate relation. |
| * |
| * In addition, if validate is true and the constraint is one we enforce |
| * on partitioned tables, allocate and return a NewConstraint structure |
| * for use in phase 3 to validate the relation (i.e., to make sure it |
| * conforms to its constraints). |
| * |
| * Note that pgcon (the ConstraintRelationId appropriately locked) |
| * is supplied externally for efficiency. No other relation should |
| * be supplied via this argument. |
| |
| */ |
| static NewConstraint * |
| constraint_apply_mapped(HeapTuple tuple, AttrMap *map, Relation cand, |
| bool validate, bool is_split, Relation pgcon) |
| { |
| Datum val; |
| bool isnull; |
| Datum *dats; |
| int16 *keys; |
| int nkeys; |
| int i; |
| Node *conexpr; |
| char *consrc; |
| char *conbin; |
| Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple); |
| NewConstraint *newcon = NULL; |
| |
| /* Translate pg_constraint.conkey */ |
| val = heap_getattr(tuple, Anum_pg_constraint_conkey, |
| RelationGetDescr(pgcon), &isnull); |
| Assert(!isnull); |
| |
| deconstruct_array(DatumGetArrayTypeP(val), |
| INT2OID, 2, true, 's', |
| &dats, NULL, &nkeys); |
| |
| keys = palloc(sizeof(int16) * nkeys); |
| for (i = 0; i < nkeys; i++) |
| { |
| int16 key = DatumGetInt16(dats[i]); |
| keys[i] = (int16)attrMap(map, key); |
| } |
| |
| /* Translate pg_constraint.conbin */ |
| val = heap_getattr(tuple, Anum_pg_constraint_conbin, |
| RelationGetDescr(pgcon), &isnull); |
| if ( !isnull ) |
| { |
| conbin = DatumGetCString(DirectFunctionCall1(textout, val)); |
| conexpr = stringToNode(conbin); |
| conexpr = attrMapExpr(map, conexpr); |
| conbin = nodeToString(conexpr); |
| } |
| else |
| { |
| conbin = NULL; |
| conexpr = NULL; |
| } |
| |
| |
| /* Don't translate pg_constraint.consrc -- per doc'n, use original */ |
| val = heap_getattr(tuple, Anum_pg_constraint_consrc, |
| RelationGetDescr(pgcon), &isnull); |
| if (!isnull) |
| { |
| consrc = DatumGetCString(DirectFunctionCall1(textout, val)); |
| } |
| else |
| { |
| consrc = NULL; |
| } |
| |
| /* Apply translated constraint to candidate. */ |
| switch ( con->contype ) |
| { |
| case CONSTRAINT_CHECK: |
| { |
| Assert( conexpr && conbin && consrc ); |
| |
| CreateConstraintEntry(NameStr(con->conname), |
| InvalidOid, |
| con->connamespace, // XXX should this be RelationGetNamespace(cand)? |
| con->contype, |
| con->condeferrable, |
| con->condeferred, |
| RelationGetRelid(cand), |
| keys, |
| nkeys, |
| InvalidOid, |
| InvalidOid, |
| NULL, |
| 0, |
| ' ', |
| ' ', |
| ' ', |
| InvalidOid, |
| conexpr, |
| conbin, |
| consrc); |
| break; |
| } |
| |
| case CONSTRAINT_FOREIGN: |
| { |
| int16 *fkeys; |
| int nfkeys; |
| Oid indexoid = InvalidOid; |
| Oid *opclasses = NULL; |
| Relation frel; |
| |
| val = heap_getattr(tuple, Anum_pg_constraint_confkey, |
| RelationGetDescr(pgcon), &isnull); |
| Assert(!isnull); |
| |
| deconstruct_array(DatumGetArrayTypeP(val), |
| INT2OID, 2, true, 's', |
| &dats, NULL, &nfkeys); |
| fkeys = palloc(sizeof(int16) * nfkeys); |
| for (i = 0; i < nfkeys; i++) |
| { |
| fkeys[i] = DatumGetInt16(dats[i]); |
| } |
| |
| frel = heap_open(con->confrelid, AccessExclusiveLock); |
| indexoid = transformFkeyCheckAttrs(frel, nfkeys, fkeys, opclasses); |
| |
| CreateConstraintEntry(NameStr(con->conname), |
| InvalidOid, |
| RelationGetNamespace(cand), |
| con->contype, |
| con->condeferrable, |
| con->condeferred, |
| RelationGetRelid(cand), |
| keys, |
| nkeys, |
| InvalidOid, |
| con->confrelid, |
| fkeys, |
| nfkeys, |
| con->confupdtype, |
| con->confdeltype, |
| con->confmatchtype, |
| indexoid, |
| NULL, /* no check constraint */ |
| NULL, |
| NULL); |
| |
| heap_close(frel, AccessExclusiveLock); |
| break; |
| } |
| case CONSTRAINT_PRIMARY: |
| case CONSTRAINT_UNIQUE: |
| { |
| /* Index-backed constraints are handled as indexes. No action here. */ |
| char *what = (con->contype == CONSTRAINT_PRIMARY)? "PRIMARY KEY" :"UNIQUE"; |
| char *who = NameStr(con->conname); |
| |
| if (is_split) |
| { |
| ; /* nothing */ |
| } |
| else if (validate) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("%s constraint \"%s\" missing", what, who), |
| errhint("Add %s constraint \"%s\" to the candidate table" |
| " or drop it from the partitioned table." |
| , what, who), |
| errOmitLocation(true))); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("WITHOUT VALIDATION incompatible with missing %s constraint \"%s\"", |
| what, who), |
| errhint("Add %s constraint %s to the candidate table" |
| " or drop it from the partitioned table." |
| , what, who), |
| errOmitLocation(true))); |
| |
| } |
| break; |
| } |
| default: |
| /* Defensive, can't occur. */ |
| elog(ERROR,"invalid constraint type: %c", con->contype); |
| break; |
| } |
| |
| newcon = NULL; |
| |
| if ( validate ) |
| { |
| switch ( con->contype ) |
| { |
| case CONSTRAINT_CHECK: |
| { |
| newcon = (NewConstraint*) palloc0(sizeof(NewConstraint)); |
| newcon->name = pstrdup(NameStr(con->conname)); |
| /* ExecQual wants implicit-AND format */ |
| newcon->qual = (Node *)make_ands_implicit((Expr *)conexpr); |
| newcon->contype = CONSTR_CHECK; |
| break; |
| } |
| case CONSTRAINT_FOREIGN: |
| { |
| elog(WARNING, "Won't enforce FK constraint."); |
| break; |
| } |
| case CONSTRAINT_PRIMARY: |
| { |
| elog(WARNING, "Won't enforce PK constraint."); |
| break; |
| } |
| case CONSTRAINT_UNIQUE: |
| { |
| elog(WARNING, "Won't enforce ND constraint."); |
| break; |
| } |
| default: |
| { |
| elog(WARNING, "!! NOT READY FOR TYPE %c CONSTRAINT !!", con->contype); |
| break; |
| } |
| } |
| } |
| return newcon; |
| } |
| |
| |
| static bool |
| relation_has_supers(Oid relid) |
| { |
| return ( |
| (caql_getcount( |
| NULL, |
| cql("SELECT count(*) FROM pg_inherits " |
| " WHERE inhrelid = :1 ", |
| ObjectIdGetDatum(relid))) > 0)); |
| } |
| |
| /* |
| * Choose a default name for a constraint on an existing partitioned table. |
| * For sanity (and visually matching constraints created by the same ALTER |
| * command) we want to avoid letting a lower-level routine pick the default |
| * name for a constraint on a partitioned table, because this would result |
| * in different names for the "same" constraint on each part. So we use |
| * this routine to forge a non-default name earlier than is done for ordinary |
| * tables. |
| * |
| * This code is modelled on code in AddRelationRawConstraints from heap.c, |
| * however, there is no actual requirement that they stay in sync. It's |
| * just nice, if they do. Here, the expression hasn't been cooked yet, so |
| * we can't use an expression tree walker to get the column names. |
| * |
| * For CHECK constraints, expr is an uncooked constraint expression. |
| * For index-backed constraints (PRIMARY KEY, UNIQUE), expr is a list of IndexElem. |
| * |
| * A small possibility of name collision still exists, however, it seems |
| * remote and will be detected and rejected later anyway. |
| */ |
| char * |
| ChooseConstraintNameForPartitionEarly(Relation rel, ConstrType contype, Node *expr) |
| { |
| char *colname = NULL; |
| char *ccname = NULL; |
| char *label = NULL; |
| |
| switch (contype) |
| { |
| case CONSTR_CHECK: |
| { |
| ParseState *dummy; |
| Node *txpr; |
| List *vars = NIL; |
| |
| dummy = make_parsestate(NULL); |
| addRTEtoQuery(dummy, |
| addRangeTableEntryForRelation(dummy, rel, NULL, false, true), |
| true, true, true); |
| txpr = transformExpr(dummy, expr); |
| vars = pull_var_clause(txpr, false); |
| |
| /* eliminate duplicates */ |
| vars = list_union(NIL, vars); |
| |
| if (list_length(vars) == 1) |
| colname = get_attname(RelationGetRelid(rel), |
| ((Var *) linitial(vars))->varattno); |
| |
| label = "check"; |
| } |
| break; |
| case CONSTR_PRIMARY: |
| /* Conventionally, we ignore column name for the PK. */ |
| label = "pkey"; |
| break; |
| case CONSTR_UNIQUE: |
| { |
| ListCell *lc; |
| IndexElem *elem; |
| |
| foreach(lc, (List*)expr) |
| { |
| elem = (IndexElem*)lfirst(lc); |
| if ( elem->name ) |
| { |
| colname = elem->name; |
| break; |
| } |
| } |
| |
| label = "key"; |
| } |
| break; |
| default: |
| elog(ERROR, "name request for constraint of inappropriate type"); |
| break; |
| } |
| |
| ccname = ChooseConstraintName(RelationGetRelationName(rel), |
| colname, |
| label, |
| RelationGetNamespace(rel), |
| NIL); |
| |
| return ccname; |
| } |
| |
| /* |
| * Preprocess a CreateStmt for a partitioned table before letting it |
| * fall into the regular tranformation code. This is an analyze-time |
| * function. |
| * |
| * At the moment, the only fixing needed is to change default constraint |
| * names to explicit ones so that they will propagate correctly through |
| * to the parts of a partitioned table. |
| */ |
| void |
| fixCreateStmtForPartitionedTable(CreateStmt *stmt) |
| { |
| ListCell *lc_elt; |
| Constraint *con; |
| List *unnamed_cons = NIL; |
| List *unnamed_cons_col = NIL; |
| List *unnamed_cons_lbl = NIL; |
| List *used_names = NIL; |
| char *no_name = ""; |
| int i; |
| |
| /* Caller should check this! */ |
| Assert(stmt->partitionBy && !stmt->is_part_child); |
| |
| foreach( lc_elt, stmt->base.tableElts ) |
| { |
| Node * elt = lfirst(lc_elt); |
| |
| switch (nodeTag(elt)) |
| { |
| case T_ColumnDef: |
| { |
| ListCell *lc_con; |
| |
| ColumnDef *cdef = (ColumnDef*)elt; |
| |
| foreach( lc_con, cdef->constraints ) |
| { |
| Node *conelt = lfirst(lc_con); |
| |
| if ( IsA(conelt, Constraint) ) |
| { |
| con = (Constraint*)conelt; |
| |
| if ( con->name ) |
| { |
| used_names = lappend(used_names, con->name); |
| continue; |
| } |
| switch (con->contype) |
| { |
| case CONSTR_CHECK: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, cdef->colname); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "check"); |
| break; |
| case CONSTR_PRIMARY: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, cdef->colname); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "pkey"); |
| break; |
| case CONSTR_UNIQUE: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, cdef->colname); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "key"); |
| break; |
| default: |
| break; |
| } |
| } |
| else |
| { |
| FkConstraint *fkcon = (FkConstraint*)conelt; |
| |
| Insist( IsA(fkcon, FkConstraint) ); |
| |
| if ( fkcon->constr_name ) |
| { |
| used_names = lappend(used_names, fkcon->constr_name); |
| continue; |
| } |
| |
| unnamed_cons = lappend(unnamed_cons, fkcon); |
| unnamed_cons_col = lappend(unnamed_cons_col, cdef->colname); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "fkey"); |
| } |
| } |
| break; |
| } |
| case T_Constraint: |
| { |
| con = (Constraint*)elt; |
| |
| if ( con->name ) |
| { |
| used_names = lappend(used_names, con->name); |
| } |
| else |
| { |
| switch (con->contype) |
| { |
| case CONSTR_CHECK: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, no_name); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "check"); |
| break; |
| case CONSTR_PRIMARY: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, no_name); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "pkey"); |
| break; |
| case CONSTR_UNIQUE: |
| unnamed_cons = lappend(unnamed_cons, con); |
| unnamed_cons_col = lappend(unnamed_cons_col, no_name); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "key"); |
| break; |
| default: |
| break; |
| } |
| } |
| break; |
| } |
| case T_FkConstraint: |
| { |
| FkConstraint *fkcon = (FkConstraint*)elt; |
| |
| unnamed_cons = lappend(unnamed_cons, fkcon); |
| unnamed_cons_col = lappend(unnamed_cons_col, no_name); |
| unnamed_cons_lbl = lappend(unnamed_cons_lbl, "fkey"); |
| |
| if ( fkcon->constr_name ) |
| { |
| used_names = lappend(used_names, fkcon->constr_name); |
| } |
| break; |
| } |
| case T_InhRelation: |
| { |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| used_names = list_union(used_names, NIL); /* eliminate dups */ |
| |
| for ( i = 0; i < list_length(unnamed_cons); i++ ) |
| { |
| char *label = list_nth(unnamed_cons_lbl, i); |
| char *colname = NULL; |
| Node *elt = list_nth(unnamed_cons, i); |
| |
| switch ( nodeTag(elt) ) |
| { |
| case T_FkConstraint: |
| { |
| FkConstraint *fcon = list_nth(unnamed_cons, i); |
| |
| fcon->constr_name = |
| ChooseConstraintNameForPartitionCreate(stmt->base.relation->relname, |
| colname, |
| label, |
| used_names); |
| used_names = lappend(used_names, fcon->constr_name); |
| break; |
| } |
| |
| case T_Constraint: |
| { |
| Constraint *con = list_nth(unnamed_cons, i); |
| |
| /* Conventionally, no column name for PK. */ |
| if ( 0 != strcmp(label, "pkey") ) |
| colname = list_nth(unnamed_cons_col, i); |
| |
| con->name = ChooseConstraintNameForPartitionCreate(stmt->base.relation->relname, |
| colname, |
| label, |
| used_names); |
| used_names = lappend(used_names,con->name); |
| |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| |
| |
| /* |
| * Subroutine for fixCreateStmtForPartitionedTable. |
| * |
| * Similar to ChooseConstraintNameForPartitionEarly but doesn't use the |
| * catalogs since we're dealing with a currently non-existent namespace |
| * (the space of constraint names on the table to be created). |
| * |
| * Modelled on ChooseConstraintName, though synchronization isn't a |
| * requirement, just a nice idea. |
| * |
| * Caller is responsible for supplying the (unqualified) relation name, |
| * optional column name (NULL or "" is okay for a table constraint), |
| * label (e.g. "check"), and list of names to avoid. |
| * |
| * Result is palloc'd and caller's responsibility. |
| */ |
| char * |
| ChooseConstraintNameForPartitionCreate(const char *rname, |
| const char *cname, |
| const char *label, |
| List *used_names) |
| { |
| int pass = 0; |
| char *conname = NULL; |
| char modlabel[NAMEDATALEN]; |
| bool found = false; |
| ListCell *lc; |
| |
| Assert(rname && *rname); |
| |
| /* Allow caller to pass "" instead of NULL for non-singular cname */ |
| if ( cname && *cname == '\0' ) |
| cname = NULL; |
| |
| /* try the unmodified label first */ |
| StrNCpy(modlabel, label, sizeof(modlabel)); |
| |
| for (;;) |
| { |
| conname = makeObjectName(rname, cname, modlabel); |
| found = false; |
| |
| foreach(lc, used_names) |
| { |
| if (strcmp((char*)lfirst(lc), conname) == 0) |
| { |
| found = true; |
| break; |
| } |
| } |
| if ( ! found ) |
| break; /* we have a winner */ |
| |
| pfree(conname); |
| snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass); |
| } |
| return conname; |
| } |
| |
| /* |
| * Determine whether the given attributes can be enforced unique within |
| * the partitioning policy of the given partitioned table. If not, issue |
| * an error. The argument primary just conditions the message text. |
| */ |
| void |
| checkUniqueConstraintVsPartitioning(Relation rel, AttrNumber *indattr, int nidxatts, bool primary) |
| { |
| int i; |
| bool contains; |
| Bitmapset *ikey = NULL; |
| Bitmapset *pkey = get_partition_key_bitmapset(RelationGetRelid(rel)); |
| |
| for (i = 0; i < nidxatts; i++) |
| ikey = bms_add_member(ikey, indattr[i]); |
| |
| contains = bms_is_subset(pkey, ikey); |
| |
| if (pkey) |
| bms_free(pkey); |
| if (ikey) |
| bms_free(ikey); |
| |
| if (! contains ) |
| { |
| char *what = "UNIQUE"; |
| |
| if ( primary ) |
| what = "PRIMARY KEY"; |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("%s constraint must contain all columns in the " |
| "partition key of relation \"%s\".", |
| what, RelationGetRelationName(rel)), |
| errhint("Include the partition key or create a part-wise UNIQUE index instead."))); |
| } |
| } |
| |
| /** |
| * Does a partition node correspond to a leaf partition? |
| */ |
| static bool IsLeafPartitionNode(PartitionNode *p) |
| { |
| Assert(p); |
| |
| /** |
| * If all of the rules have no children, this is a leaf partition. |
| */ |
| ListCell *lc = NULL; |
| foreach (lc, p->rules) |
| { |
| PartitionRule *rule = (PartitionRule *) lfirst(lc); |
| if (rule->children) |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * If default partition has children then, this is not a leaf |
| */ |
| if (p->default_part |
| && p->default_part->children) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Given a partition node, return all the associated rules, including the default partition rule if present |
| */ |
| static List* |
| get_partition_rules(PartitionNode *pn) |
| { |
| Assert(pn); |
| |
| List *result = NIL; |
| if (pn->default_part) |
| { |
| result = lappend(result, pn->default_part); |
| } |
| |
| result = list_concat(result, pn->rules); |
| |
| return result; |
| } |
| |
| /** |
| * Given a partition node, return list of children. Should not be called on a leaf partition node. |
| * |
| * Input: |
| * p - input partition node |
| * Output: |
| * List of partition nodes corresponding to its children across all rules. |
| */ |
| static List *PartitionChildren(PartitionNode *p) |
| { |
| Assert(p); |
| Assert(!IsLeafPartitionNode(p)); |
| |
| List *result = NIL; |
| |
| ListCell *lc = NULL; |
| foreach (lc, p->rules) |
| { |
| PartitionRule *rule = (PartitionRule *) lfirst(lc); |
| |
| if (rule->children) |
| { |
| result = lappend(result, rule->children); |
| } |
| } |
| |
| /** |
| * Also add default child |
| */ |
| if (p->default_part |
| && p->default_part->children) |
| { |
| result = lappend(result, p->default_part->children); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * selectPartitionMulti() |
| * |
| * Given an input partition node, values and nullness, and partition state, |
| * find matching leaf partitions. This is similar to selectPartition() with one |
| * big difference around nulls. If there is a null value corresponding to a partitioning attribute, |
| * then all children are considered matches. |
| * |
| * Output: |
| * leafPartitionOids - list of leaf partition oids, null if there are no matches |
| */ |
| List * |
| selectPartitionMulti(PartitionNode *partnode, Datum *values, bool *isnull, |
| TupleDesc tupdesc, PartitionAccessMethods *accessMethods) |
| { |
| Assert(partnode); |
| |
| List *leafPartitionOids = NIL; |
| |
| List *inputList = list_make1(partnode); |
| |
| while (list_length(inputList) > 0) |
| { |
| List *levelOutput = NIL; |
| |
| ListCell *lc = NULL; |
| foreach (lc, inputList) |
| { |
| PartitionNode *candidatePartNode = (PartitionNode *) lfirst(lc); |
| bool foundNull = false; |
| |
| for (int i = 0; i < candidatePartNode->part->parnatts; i++) |
| { |
| AttrNumber attno = candidatePartNode->part->paratts[i]; |
| |
| /** |
| * If corresponding value is null, then we should pick all of its |
| * children (or itself if it is a leaf partition) |
| */ |
| if (isnull[attno - 1]) |
| { |
| foundNull = true; |
| if (IsLeafPartitionNode(candidatePartNode)) |
| { |
| /** |
| * Extract out Oids of all children |
| */ |
| leafPartitionOids = list_concat(leafPartitionOids, all_partition_relids(candidatePartNode)); |
| } |
| else |
| { |
| levelOutput = list_concat(levelOutput, PartitionChildren(candidatePartNode)); |
| } |
| } |
| } |
| |
| /** |
| * If null was not found on the attribute, and if this is a leaf partition, |
| * then there will be an exact match. If it is not a leaf partition, then |
| * we have to find the right child to investigate. |
| */ |
| if (!foundNull) |
| { |
| if (IsLeafPartitionNode(candidatePartNode)) |
| { |
| Oid matchOid = selectPartition1(candidatePartNode, values, isnull, tupdesc, accessMethods, NULL, NULL); |
| if (matchOid != InvalidOid) |
| { |
| leafPartitionOids = lappend_oid(leafPartitionOids, matchOid); |
| } |
| } |
| else |
| { |
| PartitionNode *childPartitionNode = NULL; |
| selectPartition1(candidatePartNode, values, isnull, tupdesc, accessMethods, NULL, &childPartitionNode); |
| if (childPartitionNode) |
| { |
| levelOutput = lappend(levelOutput, childPartitionNode); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Start new level |
| */ |
| list_free(inputList); |
| inputList = levelOutput; |
| } |
| |
| return leafPartitionOids; |
| } |
| |
| /* |
| * Add a partition encoding clause for a subpartition template. We need to be |
| * able to recall these for when we later add partitions which inherit the |
| * subpartition template definition. |
| */ |
| static void |
| add_partition_encoding(Oid relid, Oid paroid, AttrNumber attnum, List *encoding) |
| { |
| Datum partoptions; |
| Datum values[Natts_pg_partition_encoding]; |
| bool nulls[Natts_pg_partition_encoding]; |
| HeapTuple tuple; |
| cqContext *pcqCtx; |
| |
| pcqCtx = |
| caql_beginscan( |
| NULL, |
| cql("INSERT INTO pg_partition_encoding ", |
| NULL)); |
| |
| Insist(attnum > 0); |
| |
| partoptions = transformRelOptions(PointerGetDatum(NULL), |
| encoding, |
| true, |
| false); |
| |
| MemSet(nulls, 0, sizeof(nulls)); |
| |
| values[Anum_pg_partition_encoding_parencoid - 1] = ObjectIdGetDatum(paroid); |
| values[Anum_pg_partition_encoding_parencattnum - 1] = Int16GetDatum(attnum); |
| values[Anum_pg_partition_encoding_parencattoptions - 1] = partoptions; |
| |
| tuple = caql_form_tuple(pcqCtx, values, nulls); |
| |
| /* Insert tuple into the relation */ |
| caql_insert(pcqCtx, tuple); /* implicit update of index as well */ |
| |
| heap_freetuple(tuple); |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| static void |
| remove_partition_encoding_entry(Oid paroid, AttrNumber attnum) |
| { |
| HeapTuple tup; |
| cqContext *pcqCtx; |
| |
| pcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_partition_encoding " |
| " WHERE parencoid = :1 " |
| " FOR UPDATE", |
| ObjectIdGetDatum(paroid))); |
| |
| while (HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| if (attnum != InvalidAttrNumber) |
| { |
| Form_pg_partition_encoding ppe = |
| (Form_pg_partition_encoding)GETSTRUCT(tup); |
| |
| if (ppe->parencattnum != attnum) |
| continue; |
| } |
| caql_delete_current(pcqCtx); |
| } |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * Remove all trace of partition encoding for a relation. This is the DROP TABLE |
| * case. May be called even if there's no entry for the partition. |
| */ |
| static void |
| remove_partition_encoding_by_key(Oid relid, AttrNumber attnum) |
| { |
| HeapTuple tup; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| |
| /* XXX XXX: not FOR UPDATE because update a child table... */ |
| |
| pcqCtx = caql_beginscan( |
| cqclr(&cqc), |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 ", |
| ObjectIdGetDatum(relid))); |
| |
| /* XXX XXX: hmm, could we add |
| " AND paristemplate = :2 ", |
| ... |
| BoolGetDatum(true))); |
| |
| Could caql use an index on parrelid even though paristemplate |
| not in index? |
| */ |
| |
| while (HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| Form_pg_partition part = (Form_pg_partition)GETSTRUCT(tup); |
| |
| if (part->paristemplate) |
| remove_partition_encoding_entry(HeapTupleGetOid(tup), attnum); |
| } |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| void |
| RemovePartitionEncodingByRelid(Oid relid) |
| { |
| remove_partition_encoding_by_key(relid, InvalidAttrNumber); |
| } |
| |
| /* |
| * Remove a partition encoding entry for a specific attribute number. |
| * May be called when no such entry actually exists. |
| */ |
| void |
| RemovePartitionEncodingByRelidAttribute(Oid relid, AttrNumber attnum) |
| { |
| remove_partition_encoding_by_key(relid, attnum); |
| } |
| |
| /* |
| * For all encoding clauses, create a pg_partition_encoding entry |
| */ |
| static void |
| add_template_encoding_clauses(Oid relid, Oid paroid, List *stenc) |
| { |
| ListCell *lc; |
| |
| foreach(lc, stenc) |
| { |
| ColumnReferenceStorageDirective *c = lfirst(lc); |
| AttrNumber attnum; |
| |
| /* |
| * Don't store default clauses since we have no need of them |
| * when we add partitions later. |
| */ |
| if (c->deflt) |
| continue; |
| |
| attnum = get_attnum(relid, strVal(c->column)); |
| |
| Insist(attnum > 0); |
| |
| add_partition_encoding(relid, paroid, attnum, c->encoding); |
| } |
| } |
| |
| Datum * |
| get_partition_encoding_attoptions(Relation rel, Oid paroid) |
| { |
| HeapTuple tup; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| Relation pgpeenc = heap_open(PartitionEncodingRelationId, |
| RowExclusiveLock); |
| Datum *opts; |
| |
| |
| opts = palloc0(sizeof(Datum) * RelationGetNumberOfAttributes(rel)); |
| |
| /* XXX XXX: should be FOR UPDATE ? why ? probably should be an |
| * AccessShare |
| */ |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), pgpeenc), |
| cql("SELECT * FROM pg_partition_encoding " |
| " WHERE parencoid = :1 ", |
| ObjectIdGetDatum(paroid))); |
| |
| while (HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| Datum paroptions; |
| AttrNumber attnum; |
| bool isnull; |
| |
| attnum = ((Form_pg_partition_encoding)GETSTRUCT(tup))->parencattnum; |
| paroptions = heap_getattr(tup, |
| Anum_pg_partition_encoding_parencattoptions, |
| RelationGetDescr(pgpeenc), |
| &isnull); |
| |
| Insist(!isnull); |
| Insist((attnum - 1) >= 0); |
| |
| opts[attnum - 1] = datumCopy(paroptions, false, -1); |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(pgpeenc, RowExclusiveLock); |
| |
| return opts; |
| } |
| |
| static List * |
| get_deparsed_partition_encodings(Oid relid, Oid paroid) |
| { |
| int i; |
| List *out = NIL; |
| Relation rel = heap_open(relid, AccessShareLock); |
| Datum *opts = get_partition_encoding_attoptions(rel, paroid); |
| |
| for (i = 0; i < RelationGetNumberOfAttributes(rel); i++) |
| { |
| if (opts[i] && !rel->rd_att->attrs[i]->attisdropped) |
| { |
| char *column; |
| ColumnReferenceStorageDirective *c = |
| makeNode(ColumnReferenceStorageDirective); |
| |
| c->encoding = untransformRelOptions(opts[i]); |
| column = get_attname(relid, i + 1); |
| c->column = makeString(column); |
| out = lappend(out, c); |
| } |
| } |
| |
| heap_close(rel, AccessShareLock); |
| |
| return out; |
| } |
| |
| /** |
| * Function that returns a string representation of partition oids. |
| * |
| * elements: an array of datums, containing oids of partitions. |
| * n: length of the elements array. |
| * |
| * Result is allocated in the current memory context. |
| */ |
| char* |
| DebugPartitionOid(Datum *elements, int n) |
| { |
| |
| StringInfoData str; |
| initStringInfo(&str); |
| appendStringInfo(&str, "{"); |
| for (int i=0; i<n; i++) |
| { |
| Oid o = DatumGetObjectId(elements[i]); |
| appendStringInfo(&str, "%s, ", get_rel_name(o)); |
| } |
| appendStringInfo(&str, "}"); |
| return str.data; |
| } |
| |
| /** |
| * Function that returns partition oids and number of partitions |
| * from the partOidHash hash table. |
| * |
| * partOidHash: a hash table with partition oids |
| * partOids: out parameter; returns a palloc'ed array of oid datums |
| * partCount: out parameter; returns the length of palloc'ed oid array |
| * |
| * The partOids are palloc'ed in current memory context. Caller should |
| * ensure timely pfree. |
| */ |
| void |
| GetSelectedPartitionOids(HTAB *partOidHash, Datum **partOids, long *partCount) |
| { |
| if (NULL != partOidHash) |
| { |
| int i = 0; |
| *partCount = hash_get_num_entries(partOidHash); |
| *partOids = (Datum *) palloc(*partCount * sizeof(Datum)); |
| |
| HASH_SEQ_STATUS pidStatus; |
| hash_seq_init(&pidStatus, partOidHash); |
| |
| Oid *pid = NULL; |
| while ((pid = (Oid *) hash_seq_search(&pidStatus)) != NULL) |
| { |
| Assert(i < *partCount); |
| (*partOids)[i++] = ObjectIdGetDatum(*pid); |
| } |
| |
| Assert(i == *partCount); |
| } |
| else |
| { |
| *partCount = 0; |
| *partOids = (Datum *) palloc(*partCount * sizeof(Datum)); |
| } |
| } |
| |
| |
| /** |
| * Prints the names of DPE selected partitions from a |
| * HTAB (pidIndex) of partition oids. |
| */ |
| void |
| LogSelectedPartitionOids(HTAB *pidIndex) |
| { |
| Datum *elements = NULL; |
| long numPartitions = 0; |
| GetSelectedPartitionOids(pidIndex, &elements, &numPartitions); |
| Assert(NULL != elements); |
| |
| elog(LOG, "DPE matched partitions: %s", DebugPartitionOid(elements, numPartitions)); |
| pfree(elements); |
| } |
| |
| /* |
| * findPartitionMetadataEntry |
| * Find PartitionMetadata object for a given partition oid from a list. |
| * |
| * Input arguments: |
| * partsMetadata: list of PartitionMetadata |
| * partOid: Part Oid |
| * partsAndRules: output parameter for matched PartitionNode |
| * accessMethods: output parameter for PartitionAccessMethods |
| * |
| */ |
| void |
| findPartitionMetadataEntry(List *partsMetadata, Oid partOid, PartitionNode **partsAndRules, PartitionAccessMethods **accessMethods) |
| { |
| ListCell *lc = NULL; |
| foreach (lc, partsMetadata) |
| { |
| PartitionMetadata *metadata = (PartitionMetadata *)lfirst(lc); |
| *partsAndRules = findPartitionNodeEntry(metadata->partsAndRules, partOid); |
| |
| if (NULL != *partsAndRules) |
| { |
| // accessMethods define the lookup access methods for partitions, one for each level |
| *accessMethods = metadata->accessMethods; |
| return; |
| } |
| } |
| } |
| |
| /* |
| * findPartitionNodeEntry |
| * Find PartitionNode object for a given partition oid |
| * |
| * Input arguments: |
| * partsMetadata: list of PartitionMetadata |
| * partOid: Part Oid |
| * return: matched PartitionNode |
| */ |
| static PartitionNode * |
| findPartitionNodeEntry(PartitionNode *partitionNode, Oid partOid) |
| { |
| if (NULL == partitionNode) |
| { |
| return NULL; |
| } |
| |
| Assert(NULL != partitionNode->part); |
| if (partitionNode->part->parrelid == partOid) |
| { |
| return partitionNode; |
| } |
| |
| /* |
| * check recursively in child parts in case we have the oid of an |
| * intermediate node |
| */ |
| PartitionNode *childNode = NULL; |
| ListCell *lcChild = NULL; |
| foreach (lcChild, partitionNode->rules) |
| { |
| PartitionRule *childRule = (PartitionRule *) lfirst(lcChild); |
| childNode = findPartitionNodeEntry(childRule->children, partOid); |
| if (NULL != childNode) |
| { |
| return childNode; |
| } |
| } |
| |
| /* |
| * check recursively in the default part, if any |
| */ |
| if (NULL != partitionNode->default_part) |
| { |
| childNode = findPartitionNodeEntry(partitionNode->default_part->children, partOid); |
| } |
| |
| return childNode; |
| } |
| |
| /* |
| * createValueArrays |
| * Create an Datum/bool array that will be used to populate partition key value. |
| * |
| * The size of this array is based on the attribute number of the partition key. |
| */ |
| void |
| createValueArrays(int keyAttno, Datum **values, bool **isnull) |
| { |
| *values = palloc0(keyAttno * sizeof(Datum)); |
| *isnull = palloc(keyAttno * sizeof(bool)); |
| Assert (NULL != values); |
| Assert (NULL != isnull); |
| |
| MemSet(*isnull, true, keyAttno * sizeof(bool)); |
| } |
| |
| /* |
| * freeValueArrays |
| * Free Datum/bool array. |
| */ |
| void |
| freeValueArrays(Datum *values, bool *isnull) |
| { |
| Assert (NULL != values); |
| Assert (NULL != isnull); |
| pfree(values); |
| pfree(isnull); |
| } |