| /* |
| * 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. |
| */ |
| |
| /*-------------------------------------------------------------------------- |
| * |
| * cdbpartindex.c |
| * Provides utility routines to support indexes over partitioned tables |
| * within Greenplum Database. |
| * |
| *-------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| #include "funcapi.h" |
| #include "fmgr.h" |
| #include "catalog/catquery.h" |
| #include "access/hash.h" |
| #include "catalog/index.h" |
| #include "catalog/pg_constraint.h" |
| #include "catalog/pg_partition_encoding.h" |
| #include "catalog/pg_partition_rule.h" |
| #include "cdb/cdbpartition.h" |
| #include "cdb/cdbvars.h" |
| #include "commands/tablecmds.h" |
| #include "optimizer/planmain.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| |
| /* initial estimate for number of logical indexes */ |
| #define INITIAL_NUM_LOGICAL_INDEXES_ESTIMATE 100 |
| |
| static int logicalIndexId = 1; |
| static int numLogicalIndexes = 0; |
| static int numIndexesOnDefaultParts = 0; |
| |
| /* |
| * hash table to identify distinct "logical indexes". Each index with the same |
| * index key, index pred, index expr is considered equivalent. |
| */ |
| static HTAB *PartitionIndexHash; |
| |
| /* hash table to hold information about distinct logical indexes. */ |
| static HTAB *LogicalIndexInfoHash; |
| |
| /* |
| * Hash entry for PartionIndexHash |
| * hashkey is (indexkey + indexpred + indexexprs) |
| */ |
| typedef struct |
| { |
| char *key; |
| int logicalIndexId; |
| } PartitionIndexHashEntry; |
| |
| /* |
| * Hashentry for LogicalIndexInfoHash |
| * hashkey is (logicalIndexId) |
| */ |
| typedef struct |
| { |
| char *key; |
| int logicalIndexId; |
| Oid logicalIndexOid; |
| struct IndexInfo *ii; |
| LogicalIndexType indType; |
| List *partList; |
| List *defaultPartList; |
| } LogicalIndexInfoHashEntry; |
| |
| /* |
| * Each node in the PartitionIndexNode tree describes a partition |
| * and a list of it's logical indexes |
| */ |
| typedef struct PartitionIndexNode |
| { |
| Oid paroid; /* OID of row in pg_partition */ |
| Oid parparentrule; /* OID of row in pg_partition_rule */ |
| Oid parchildrelid; /* table oid of the part */ |
| Oid parrelid; /* needed to map attnums */ |
| bool isDefault; /* is this a default part */ |
| Bitmapset *index; /* logical indexes for this part */ |
| List *children; /* child parts */ |
| List *defaultLevels; /* the level at which the part key is default */ |
| } PartitionIndexNode; |
| |
| static void recordIndexesOnLeafPart(PartitionIndexNode **pNodePtr, |
| Oid partOid, Oid rootOid, |
| Relation indexRel); |
| static void recordIndexes(PartitionIndexNode **partIndexTree, |
| Relation indexRel); |
| static void getPartitionIndexNode(Oid rootOid, int2 level, |
| Oid parent, PartitionIndexNode **n, |
| bool isDefault, List *defaultLevels); |
| static void indexParts(PartitionIndexNode **np, bool isDefault); |
| static void dumpPartsIndexInfo(PartitionIndexNode *n, int level); |
| static LogicalIndexes * createPartsIndexResult(Oid root); |
| static bool collapseIndexes(PartitionIndexNode **partitionIndexNode, |
| LogicalIndexInfoHashEntry **entry); |
| static void createIndexHashTables(void); |
| static IndexInfo *populateIndexInfo(cqContext *pcqCtx, HeapTuple tuple, |
| Form_pg_index indForm); |
| static Node *mergeIntervals(Node *intervalFst, Node *intervalSnd); |
| static void extractStartEndRange(Node *clause, Node **ppnodeStart, Node **ppnodeEnd); |
| static void extractOpExprComponents(OpExpr *opexpr, Var **ppvar, Const **ppconst, Oid *opno); |
| |
| /* |
| * TODO: similar routines in cdbpartition.c. Move up to cdbpartition.h ? |
| */ |
| |
| /* 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(((PartitionIndexHashEntry*)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 */ |
| } |
| |
| /* |
| * BuildLogicalIndexInfo |
| * Returns an array of "logical indexes" on partitioned |
| * tables. |
| * |
| * Memory for result is allocated from caller context. |
| * |
| * If input relid does not represent a partitioned table, the function |
| * returns NULL. |
| * |
| * This function does the following |
| * 1) Builds a PartitionIndexNode tree representing the partitioned |
| * table hierarchy. |
| * 2) Builds 2 hash tables - |
| * - 1st hash table is used to find the logical indexes in a |
| * partitioning tree hierarchy - a logical |
| * index corresponds to multiple physical indexes each |
| * of which have the same index key + index pred + index exprs. Each |
| * logical index is assigned a unique logicalIndexId. |
| * - The 2nd hash table table is used to hold information associated |
| * with a logical index for easy lookup. |
| * 3) Annotates leaves of hierarchy with logical index ids. |
| * See recordIndexes function comments. |
| * 4) Consolidates logical index information i.e. if a logical index exists on all |
| * child parts of a parent part, the parent part is annotated with the logical |
| * index id and logical index id is cleared from child parts. |
| * 5) For each logical index, OR the check constraints of the part on which |
| * the index exists (i.e. generate a partial index predicate for each |
| * logical index) |
| * 6) Since default parts, don't have constraints associated with them, |
| * each logical index on a default part is represented as a separate |
| * logical index in the output. Some additional information is |
| * included so the specific default partition may be identified. |
| * |
| * Example of a partitioning hierarchy, and the output generated. |
| * I1..In are logical index ids. |
| * |
| * After Step 3 (leaves annotated with logical index ids) |
| * |
| * Root |
| * / | \ |
| * / | \ |
| * / | \ |
| * A1 A2 default - pk1 is partkey at level 0 |
| * / \ / \ / \ |
| * / \ / \ / \ |
| * / \ / \ / \ |
| * B1(I1,I2) def(I1,I2) B1(I1,I2) def(I2) B1(I1,I2,I4) def(I1,I2,I3) - pk2 is partkey at level 1 |
| * |
| * |
| * After step 4 - (consolidation of logical indexes) |
| * |
| * Root (I2) |
| * / | \ |
| * / | \ |
| * / | \ |
| * A1 (I1) A2 default (I1) |
| * / \ / \ / \ |
| * / \ / \ / \ |
| * / \ / \ / \ |
| * B1 def B1(I1) def B1(I4) def(I3) |
| * |
| * The output returned from BuildLogicalIndexInfo will be |
| * |
| * logicalIndexOid nColumns indexKeys indPred indExprs indIsUnique partCons defaultLevels |
| * I2 .. .. .. .. .. - - |
| * I1 .. .. .. .. .. pk1=A1 OR (pk1=A2 AND pk2=B1) - |
| * I1' .. .. .. .. .. - 0 |
| * I3 .. .. .. .. .. - 0,1 |
| * I4 .. .. .. .. .. pk2=B1 0 |
| */ |
| LogicalIndexes * |
| BuildLogicalIndexInfo(Oid relid) |
| { |
| MemoryContext callerContext = NULL; |
| MemoryContext partContext = NULL; |
| LogicalIndexes *partsLogicalIndexes = NULL; |
| Relation indexRel; |
| HASH_SEQ_STATUS hash_seq; |
| LogicalIndexInfoHashEntry *entry; |
| PartitionIndexNode *n = NULL; |
| |
| /* |
| * create a memory context to hold allocations, so we can get rid of |
| * at the end of this function. |
| */ |
| partContext = AllocSetContextCreate(CurrentMemoryContext, |
| "Partitioned Logical Index Context", |
| ALLOCSET_DEFAULT_MINSIZE, |
| ALLOCSET_DEFAULT_INITSIZE, |
| ALLOCSET_DEFAULT_MAXSIZE); |
| |
| /* switch to the new context */ |
| callerContext = MemoryContextSwitchTo(partContext); |
| |
| /* initialize globals */ |
| logicalIndexId = 1; |
| numLogicalIndexes = 0; |
| numIndexesOnDefaultParts = 0; |
| |
| /* construct the partition node tree */ |
| getPartitionIndexNode(relid, 0, InvalidOid, &n, false, NIL); |
| |
| if (!n) |
| return NULL; |
| |
| /* create the hash tables to hold the logical index info */ |
| createIndexHashTables(); |
| |
| /* open pg_index and pass it to recordIndexes */ |
| indexRel = heap_open(IndexRelationId, AccessShareLock); |
| |
| /* now walk the tree and annotate with logical index id at each leaf */ |
| recordIndexes(&n, indexRel); |
| |
| heap_close(indexRel, AccessShareLock); |
| hash_freeze(LogicalIndexInfoHash); |
| |
| /* get rid of first hash table here as we have identified all logical indexes */ |
| hash_destroy(PartitionIndexHash); |
| |
| /* walk the tree and pull-up indexes for each logical index */ |
| hash_seq_init(&hash_seq, LogicalIndexInfoHash); |
| while ((entry = hash_seq_search(&hash_seq))) |
| { |
| collapseIndexes(&n, &entry); |
| numLogicalIndexes++; |
| } |
| |
| dumpPartsIndexInfo(n, 0); |
| |
| /* associate index with parts */ |
| indexParts(&n, false); |
| |
| /* switch to caller context and handle results */ |
| MemoryContextSwitchTo(callerContext); |
| |
| /* generate output rows */ |
| if ((numLogicalIndexes+numIndexesOnDefaultParts) > 0) |
| partsLogicalIndexes = createPartsIndexResult(relid); |
| |
| hash_destroy(LogicalIndexInfoHash); |
| |
| /* all information has been copied into caller context */ |
| MemoryContextDelete(partContext); |
| |
| return partsLogicalIndexes; |
| } |
| |
| /* |
| * getPartitionIndexNode |
| * Construct a PartitionIndexNode tree for the given part. |
| * Recurs to construct branches. |
| */ |
| static void |
| getPartitionIndexNode(Oid rootOid, |
| int2 level, |
| Oid parent, |
| PartitionIndexNode **n, |
| bool isDefault, |
| List *defaultLevels) |
| { |
| HeapTuple tuple; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| Relation partRel; |
| Relation partRuleRel; |
| Form_pg_partition partDesc; |
| Oid parOid; |
| bool inctemplate = false; |
| |
| if (Gp_role != GP_ROLE_DISPATCH) |
| return; |
| |
| /* select oid as partid, * |
| * from pg_partition |
| * where |
| * parrelid = :relid and |
| * parlevel = :level and |
| * paristemplate = false; |
| */ |
| partRel = heap_open(PartitionRelationId, AccessShareLock); |
| |
| tuple = caql_getfirst( |
| caql_addrel(cqclr(&cqc), partRel), |
| cql("SELECT * FROM pg_partition " |
| " WHERE parrelid = :1 " |
| " AND parlevel = :2 " |
| " AND paristemplate = :3", |
| ObjectIdGetDatum(rootOid), |
| Int16GetDatum(level), |
| Int16GetDatum(inctemplate))); |
| |
| if (HeapTupleIsValid(tuple)) |
| { |
| parOid = HeapTupleGetOid(tuple); |
| partDesc = (Form_pg_partition) GETSTRUCT(tuple); |
| |
| if (level == 0) |
| { |
| Assert(parent == InvalidOid); |
| Assert(*n == NULL); |
| |
| /* handle root part specially */ |
| *n = palloc0(sizeof(PartitionIndexNode)); |
| (*n)->paroid = parOid; |
| (*n)->parrelid = (*n)->parchildrelid = partDesc->parrelid; |
| (*n)->isDefault = false; |
| } |
| heap_close(partRel, AccessShareLock); |
| } |
| else |
| { |
| heap_close(partRel, AccessShareLock); |
| return; |
| } |
| |
| |
| /* recurse to fill the children */ |
| partRuleRel = heap_open(PartitionRuleRelationId, AccessShareLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), partRuleRel), |
| cql("SELECT * FROM pg_partition_rule " |
| " WHERE paroid = :1 " |
| " AND parparentrule = :2 ", |
| ObjectIdGetDatum(parOid), |
| ObjectIdGetDatum(parent))); |
| |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| PartitionIndexNode *child; |
| Form_pg_partition_rule rule_desc = (Form_pg_partition_rule)GETSTRUCT(tuple); |
| |
| child = palloc(sizeof(PartitionIndexNode)); |
| memset(child, 0, sizeof(PartitionIndexNode)); |
| child->paroid = HeapTupleGetOid(tuple); |
| child->parrelid = partDesc->parrelid; |
| child->parchildrelid = rule_desc->parchildrelid; |
| |
| /* For every node, we keep track of every level at which this node has default partitioning */ |
| child->isDefault = isDefault; |
| child->defaultLevels = defaultLevels; |
| |
| /* current part is a default part */ |
| if (rule_desc->parisdefault) |
| { |
| child->isDefault = true; |
| child->defaultLevels = lappend_int(child->defaultLevels, partDesc->parlevel); |
| } |
| |
| /* insert child into children */ |
| (*n)->children = lappend((*n)->children, child); |
| |
| getPartitionIndexNode(rootOid, level + 1, HeapTupleGetOid(tuple), |
| &child, child->isDefault, child->defaultLevels); |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(partRuleRel, AccessShareLock); |
| } |
| |
| /* |
| * constructIndexHashKey |
| * The index hash key is a string representing the index key, |
| * index predicate, and index exprs |
| * |
| * Note, all attnums are mapped to the root. |
| */ |
| static char * |
| constructIndexHashKey(Oid partOid, |
| Oid rootOid, |
| HeapTuple tup, |
| AttrNumber *attMap, |
| IndexInfo *ii, |
| LogicalIndexType indType) |
| { |
| StringInfoData buf; |
| Expr *predExpr = NULL; |
| Expr *exprsExpr = NULL; |
| |
| initStringInfo(&buf); |
| |
| /* map the attrnos in the indKey to root part */ |
| for (int i = 0; i < ii->ii_NumIndexAttrs; i++) |
| { |
| if (attMap && ii->ii_KeyAttrNumbers[i] != 0) |
| { |
| ii->ii_KeyAttrNumbers[i] = attMap[(ii->ii_KeyAttrNumbers[i]) - 1]; |
| } |
| appendStringInfo(&buf, "%d", ii->ii_KeyAttrNumbers[i]); |
| } |
| |
| /* map the attrnos in indPred */ |
| predExpr = (Expr *)ii->ii_Predicate; |
| |
| change_varattnos_of_a_node((Node *)predExpr, attMap); |
| |
| appendStringInfo(&buf, "%s", nodeToString(predExpr)); |
| |
| /* remember mapped predicate */ |
| ii->ii_Predicate = (List *)predExpr; |
| |
| exprsExpr = (Expr *)ii->ii_Expressions; |
| |
| change_varattnos_of_a_node((Node *)exprsExpr, attMap); |
| |
| appendStringInfo(&buf, "%s", nodeToString(exprsExpr)); |
| appendStringInfo(&buf, "%d", (int) indType); |
| |
| /* remember mapped exprsList */ |
| ii->ii_Expressions = (List *)exprsExpr; |
| |
| return buf.data; |
| } |
| |
| /* |
| * createIndexHashTables |
| * create the hash tables to hold logical indexes |
| */ |
| static void |
| createIndexHashTables() |
| { |
| MemoryContext context = NULL; |
| HASHCTL hash_ctl; |
| |
| /* |
| * this hash is to identify the logical indexes. 1 logical index corresponds to |
| * multiple physical indexes which have the same indkey + indpred + indexprs |
| */ |
| memset(&hash_ctl, 0, sizeof(hash_ctl)); |
| hash_ctl.keysize = sizeof(char *); |
| hash_ctl.entrysize = sizeof(PartitionIndexHashEntry); |
| hash_ctl.hash = key_string_hash; |
| hash_ctl.match = key_string_compare; |
| hash_ctl.keycopy = key_string_copy; |
| hash_ctl.hcxt = context; |
| PartitionIndexHash = hash_create("Partition Index Hash", |
| INITIAL_NUM_LOGICAL_INDEXES_ESTIMATE, |
| &hash_ctl, |
| HASH_ELEM | HASH_FUNCTION | |
| HASH_COMPARE | HASH_KEYCOPY | |
| HASH_CONTEXT); |
| |
| Assert(PartitionIndexHash != NULL); |
| |
| /* this hash is used to hold the logical indexes for easy lookup */ |
| memset(&hash_ctl, 0, sizeof(hash_ctl)); |
| hash_ctl.keysize = sizeof(uint32); |
| hash_ctl.entrysize = sizeof(LogicalIndexInfoHashEntry); |
| hash_ctl.hash = tag_hash; |
| hash_ctl.hcxt = context; |
| LogicalIndexInfoHash = hash_create("Logical Index Info Hash", |
| INITIAL_NUM_LOGICAL_INDEXES_ESTIMATE, |
| &hash_ctl, |
| HASH_ELEM | HASH_FUNCTION ); |
| Assert(LogicalIndexInfoHash != NULL); |
| } |
| |
| /* |
| * recordIndexes |
| * Invoke the recordIndexesOnPart routine on every leaf partition. |
| */ |
| static void |
| recordIndexes(PartitionIndexNode **partIndexTree, Relation indexRel) |
| { |
| ListCell *lc; |
| PartitionIndexNode *partIndexNode = *partIndexTree; |
| |
| if (partIndexNode->children) |
| { |
| foreach (lc, partIndexNode->children) |
| { |
| PartitionIndexNode *child = (PartitionIndexNode *) lfirst(lc); |
| recordIndexes(&child, indexRel); |
| } |
| } |
| else |
| /* at a leaf */ |
| recordIndexesOnLeafPart(partIndexTree, |
| partIndexNode->parchildrelid, |
| partIndexNode->parrelid, indexRel); |
| } |
| |
| /* |
| * recordIndexesOnLeafPart |
| * Given a part |
| * - fetch each index on the part from pg_index |
| * - map attnums in index key, index expr, index pred to the root attnums |
| * - insert the logical index into the PartitionIndexHash using the |
| * index key, index exprs, index pred as hash key. |
| * - fetch the associated logical index id from the hash and record it in the tree. |
| * - insert the logical index into the LogicalIndexInfo Hash using the logical index id |
| * as the hash key. |
| */ |
| static void |
| recordIndexesOnLeafPart(PartitionIndexNode **pNodePtr, |
| Oid partOid, |
| Oid rootOid, |
| Relation indexRel) |
| { |
| HeapTuple tuple; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| char *partIndexHashKey; |
| bool foundPartIndexHash; |
| bool foundLogicalIndexHash; |
| AttrNumber *attmap = NULL; |
| struct IndexInfo *ii = NULL; |
| PartitionIndexHashEntry *partIndexHashEntry; |
| LogicalIndexInfoHashEntry *logicalIndexInfoHashEntry; |
| PartitionIndexNode *pNode = *pNodePtr; |
| |
| Relation partRel = heap_open(partOid, AccessShareLock); |
| |
| char relstorage = partRel->rd_rel->relstorage; |
| |
| heap_close(partRel, AccessShareLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), indexRel), |
| cql("SELECT * FROM pg_index " |
| " WHERE indrelid = :1", |
| ObjectIdGetDatum(partOid))); |
| |
| /* fetch each index on part */ |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| Form_pg_index indForm = (Form_pg_index) GETSTRUCT(tuple); |
| |
| LogicalIndexType indType = INDTYPE_BITMAP; |
| /* for AO and AOCO tables we assume the index is bitmap, for heap partitions |
| * look up the access method from the catalog |
| */ |
| if (RELSTORAGE_HEAP == relstorage) |
| { |
| |
| Relation indRel = RelationIdGetRelation(indForm->indexrelid); |
| Assert(NULL != indRel); |
| if (BTREE_AM_OID == indRel->rd_rel->relam) |
| { |
| indType = INDTYPE_BTREE; |
| } |
| RelationClose(indRel); |
| } |
| |
| /* |
| * when constructing hash key, we need to map attnums in part indexes |
| * to root attnums. Get the attMap needed for mapping. |
| */ |
| if (!attmap) |
| { |
| Relation partRel = heap_open(partOid, AccessShareLock); |
| Relation rootRel = heap_open(rootOid, AccessShareLock); |
| |
| TupleDesc rootTupDesc = rootRel->rd_att; |
| TupleDesc partTupDesc = partRel->rd_att; |
| |
| attmap = varattnos_map(partTupDesc, rootTupDesc); |
| |
| /* can we close here ? */ |
| heap_close(partRel, AccessShareLock); |
| heap_close(rootRel, AccessShareLock); |
| } |
| |
| |
| /* populate index info structure */ |
| ii = populateIndexInfo(pcqCtx, tuple, indForm); |
| |
| /* construct hash key for the index */ |
| partIndexHashKey = constructIndexHashKey(partOid, rootOid, tuple, attmap, ii, indType); |
| |
| /* lookup PartitionIndexHash table */ |
| partIndexHashEntry = (PartitionIndexHashEntry *)hash_search(PartitionIndexHash, |
| (void *)partIndexHashKey, |
| HASH_ENTER, |
| &foundPartIndexHash); |
| |
| if (!foundPartIndexHash) |
| { |
| /* first time seeing this index */ |
| partIndexHashEntry->key = partIndexHashKey; |
| |
| /* assign a logical index id */ |
| partIndexHashEntry->logicalIndexId = logicalIndexId++; |
| |
| /* insert the entry into LogicalIndexInfoHash to keep track of logical indexes */ |
| logicalIndexInfoHashEntry = (LogicalIndexInfoHashEntry *)hash_search(LogicalIndexInfoHash, |
| (void *)&(partIndexHashEntry->logicalIndexId), |
| HASH_ENTER, |
| &foundLogicalIndexHash); |
| |
| |
| if (foundLogicalIndexHash) |
| { |
| /* |
| * we should not find the entry in the logical index hash as this is the first |
| * time we are seeing it |
| */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INTERNAL_ERROR), |
| errmsg("error during BuildLogicalIndexInfo. Found indexrelid \"%d\" in hash", |
| indForm->indexrelid |
| ))); |
| } |
| |
| logicalIndexInfoHashEntry->logicalIndexId = partIndexHashEntry->logicalIndexId; |
| logicalIndexInfoHashEntry->logicalIndexOid = indForm->indexrelid; |
| logicalIndexInfoHashEntry->ii = ii; |
| logicalIndexInfoHashEntry->partList = NIL; |
| logicalIndexInfoHashEntry->defaultPartList = NIL; |
| logicalIndexInfoHashEntry->indType = indType; |
| } |
| else |
| { |
| /* we can release IndexInfo as we already have the information in the hash */ |
| if (ii->ii_Expressions) |
| pfree(ii->ii_Expressions); |
| if (ii->ii_Predicate) |
| pfree(ii->ii_Predicate); |
| pfree(ii); |
| } |
| |
| |
| /* update the PartitionIndexNode -> index bitmap */ |
| pNode->index = bms_add_member(pNode->index, partIndexHashEntry->logicalIndexId); |
| } |
| |
| caql_endscan(pcqCtx); |
| } |
| |
| /* |
| * collapseIndexes |
| * Walk the PartitionNode tree |
| * - For every node |
| * - If a logical index exists on all the children of the node, record the index |
| * on the node and wipeout the index from the children |
| * - recurse up the tree to the root. |
| */ |
| static bool |
| collapseIndexes(PartitionIndexNode **partitionIndexNode, |
| LogicalIndexInfoHashEntry **entry) |
| { |
| ListCell *lc, *lc1; |
| PartitionIndexNode *pn = *partitionIndexNode; |
| bool exists = TRUE; |
| int lid= (*entry)->logicalIndexId; |
| int childStatus = TRUE; |
| |
| if (pn->children) |
| { |
| foreach(lc, pn->children) |
| { |
| PartitionIndexNode *child = (PartitionIndexNode *) lfirst(lc); |
| |
| /* not a leaf, so check the children */ |
| exists = collapseIndexes(&child, entry); |
| |
| /* For every logical index we are traversing the WHOLE tree. */ |
| if (!exists) |
| { |
| /* there is a child that doesn't have the index */ |
| childStatus = FALSE; |
| } |
| } |
| |
| if (childStatus) |
| { |
| /* if index exists on every child, record in the parent */ |
| pn->index = bms_add_member(pn->index, lid); |
| |
| /* remove the index from the children */ |
| foreach (lc1, pn->children) |
| { |
| PartitionIndexNode *child = (PartitionIndexNode *) lfirst(lc1); |
| bms_del_member(child->index, lid); |
| } |
| } |
| } |
| else |
| { |
| /* a leaf - check if index exists */ |
| if (!bms_is_member(lid, pn->index)) |
| exists = FALSE; |
| } |
| |
| return exists; |
| } |
| |
| /* |
| * indexParts |
| * Walks the tree of nodes and for each logical index that exists on the node, |
| * records the node (partition id) with the logical index in the hash. |
| * If the logical index exists on a default part, record some additional |
| * information to indentify the specific default part. |
| * |
| * This consolidates all the information regarding the logical index in the |
| * hash table. As we exit from the function we have a hash table with an entry |
| * for every logical index |
| * logical index id -> (logicalIndexOid, IndexInfo, partList, defaultPartList) |
| */ |
| static void indexParts(PartitionIndexNode **np, bool isDefault) |
| { |
| LogicalIndexInfoHashEntry *entry; |
| PartitionIndexNode *n = *np; |
| bool found; |
| |
| if (n) |
| { |
| int x; |
| x = bms_first_from(n->index, 0); |
| while (x >= 0) |
| { |
| entry = (LogicalIndexInfoHashEntry *)hash_search(LogicalIndexInfoHash, |
| (void *)&x, |
| HASH_FIND, |
| &found); |
| if (!found) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_INTERNAL_ERROR), |
| errmsg("error during BuildLogicalIndexInfo. Indexr not found \"%d\" in hash", |
| x))); |
| } |
| |
| if (n->isDefault || isDefault) |
| { |
| /* |
| * We keep track of the default node in the PartitionIndexNode |
| * tree, since we need to output the defaultLevels information |
| * to the caller. |
| */ |
| entry->defaultPartList = lappend(entry->defaultPartList, n); |
| numIndexesOnDefaultParts++; |
| } |
| else |
| /* |
| * For regular non-default parts we just track the part oid |
| * which will be used to get the part constraint. |
| */ |
| entry->partList = lappend_oid(entry->partList, n->parchildrelid); |
| x = bms_first_from(n->index, x + 1); |
| } |
| } |
| |
| if (n->children) |
| { |
| ListCell *lc; |
| foreach (lc, n->children) |
| { |
| PartitionIndexNode *child = (PartitionIndexNode *) lfirst(lc); |
| indexParts(&child, n->isDefault); |
| } |
| } |
| } |
| |
| /* |
| * getPartConstraints |
| * Given an OID, returns a Node that represents all the check constraints |
| * on the table AND'd together, only if these constraints cover the keys in |
| * the given list. Otherwise, it returns NULL |
| */ |
| static Node * |
| getPartConstraints(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))); |
| |
| // list of keys referenced in the found constraints |
| List *keys = NIL; |
| |
| 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); |
| |
| if (result) |
| result = (Node *)make_andclause(list_make2(result, conExpr)); |
| else |
| result = 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; |
| |
| // 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]); |
| keys = lappend_int(keys, key_elem); |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(conRel, AccessShareLock); |
| |
| ListCell *lc = NULL; |
| foreach (lc, partKey) |
| { |
| int partKeyCol = lfirst_int(lc); |
| |
| if (list_find_int(keys, partKeyCol) < 0) |
| { |
| // passed in key is not found in the constraint. return NULL |
| if (NULL != result) |
| { |
| pfree(result); |
| } |
| |
| result = NULL; |
| break; |
| } |
| } |
| |
| list_free(keys); |
| return result; |
| } |
| |
| /* |
| * get_relation_part_constraints |
| * return the part constraints for a partitioned table given the oid of the root |
| */ |
| Node * |
| get_relation_part_constraints(Oid rootOid, List **defaultLevels) |
| { |
| if (!rel_is_partitioned(rootOid)) |
| { |
| return NULL; |
| } |
| |
| // get number of partitioning levels |
| List *partkeys = rel_partition_keys_ordered(rootOid); |
| int nLevels = list_length(partkeys); |
| |
| Node *allCons = NULL; |
| for (int level = 0; level < nLevels; level++) |
| { |
| List *partKey = (List *) list_nth(partkeys, level); |
| |
| PartitionNode *pn = get_parts(rootOid, level, 0 /*parent*/, false /* inctemplate */, CurrentMemoryContext, false /*includesubparts*/); |
| |
| Assert(NULL != pn); |
| List *partOids = all_partition_relids(pn); |
| |
| Node *partCons = NULL; |
| ListCell *lc = NULL; |
| foreach(lc, partOids) |
| { |
| Oid partOid = lfirst_oid(lc); |
| // fetch part constraint mapped to root |
| partCons = getPartConstraints(partOid, rootOid, partKey); |
| |
| if (NULL == partCons) |
| { |
| // default partition has NULL constriant in GPDB's catalog |
| *defaultLevels = lappend_int(*defaultLevels, level); |
| continue; |
| } |
| |
| // OR them to current constraints |
| if (NULL == allCons) |
| { |
| allCons = partCons; |
| } |
| else |
| { |
| allCons = (Node *) mergeIntervals(allCons, partCons); |
| } |
| } |
| } |
| |
| if (NULL == allCons) |
| { |
| allCons = makeBoolConst(false /*value*/, false /*isnull*/); |
| } |
| |
| list_free(partkeys); |
| return allCons; |
| } |
| |
| /* |
| * populateIndexInfo |
| * Populate the IndexInfo structure with the information from pg_index tuple. |
| */ |
| static IndexInfo * |
| populateIndexInfo(cqContext *pcqCtx, |
| HeapTuple indTuple, |
| Form_pg_index indForm) |
| { |
| IndexInfo *ii; |
| Datum datum; |
| bool isnull = false; |
| char *str; |
| |
| /* |
| * We could just call existing BuildIndexInfo here. But BuildIndexInfo needs to open each index, |
| * and populate the relcache entry for the index relation. We can get the information by |
| * just looking into pg_index tuple. |
| */ |
| ii = makeNode(IndexInfo); |
| |
| ii->ii_Unique = indForm->indisunique; |
| ii->ii_NumIndexAttrs = indForm->indnatts; |
| |
| for (int i = 0; i < indForm->indnatts; i++) |
| { |
| ii->ii_KeyAttrNumbers[i] = indForm->indkey.values[i]; |
| } |
| |
| ii->ii_Expressions = NIL; |
| datum = caql_getattr(pcqCtx, Anum_pg_index_indexprs, &isnull); |
| if (!isnull) |
| { |
| str = TextDatumGetCString(datum); |
| ii->ii_Expressions = (List *)stringToNode(str); |
| pfree(str); |
| fix_opfuncids((Node *) ii->ii_Expressions); |
| } |
| |
| isnull = false; |
| ii->ii_Predicate = NIL; |
| datum = caql_getattr(pcqCtx, Anum_pg_index_indpred, &isnull); |
| if (!isnull) |
| { |
| str = TextDatumGetCString(datum); |
| ii->ii_Predicate = (List *)stringToNode(str); |
| pfree(str); |
| fix_opfuncids((Node *) ii->ii_Predicate); |
| } |
| |
| return ii; |
| } |
| |
| /* |
| * generateLogicalIndexPred |
| * generate the partial index predicate associated with each logical index. |
| * The predicate is generated by OR'ing the constraints of all parts on which the logical |
| * index exists. |
| */ |
| static void |
| generateLogicalIndexPred(LogicalIndexes *li, |
| int *curIdx, |
| LogicalIndexInfoHashEntry *entry, |
| Oid root, |
| int *numLogicalIndexes) |
| { |
| Node *conList; |
| ListCell *lc; |
| AttrNumber *indexKeys; |
| |
| if (list_length(entry->partList) > 0) |
| { |
| /* populate the index information */ |
| li->logicalIndexInfo[*curIdx]->partCons = NULL; |
| li->logicalIndexInfo[*curIdx]->logicalIndexOid = entry->logicalIndexOid; |
| li->logicalIndexInfo[*curIdx]->nColumns = entry->ii->ii_NumIndexAttrs; |
| indexKeys = palloc(sizeof(AttrNumber) * entry->ii->ii_NumIndexAttrs); |
| memcpy((char *)indexKeys, |
| &(entry->ii->ii_KeyAttrNumbers), sizeof(AttrNumber) * entry->ii->ii_NumIndexAttrs); |
| li->logicalIndexInfo[*curIdx]->indexKeys = indexKeys; |
| li->logicalIndexInfo[*curIdx]->indExprs = (List *)copyObject(entry->ii->ii_Expressions); |
| li->logicalIndexInfo[*curIdx]->indPred = (List *)copyObject(entry->ii->ii_Predicate); |
| li->logicalIndexInfo[*curIdx]->indIsUnique = entry->ii->ii_Unique; |
| li->logicalIndexInfo[*curIdx]->indType = entry->indType; |
| |
| /* fetch the partList from the logical index hash entry */ |
| foreach(lc, entry->partList) |
| { |
| Oid partOid = lfirst_oid(lc); |
| |
| if (partOid != root) |
| { |
| /* fetch part constraint mapped to root */ |
| conList = getPartConstraints(partOid, root, NIL /*partKey*/); |
| |
| /* OR them to current constraints */ |
| if (li->logicalIndexInfo[*curIdx]->partCons) |
| { |
| li->logicalIndexInfo[*curIdx]->partCons = mergeIntervals(li->logicalIndexInfo[*curIdx]->partCons, conList); |
| } |
| else |
| { |
| li->logicalIndexInfo[*curIdx]->partCons = conList; |
| } |
| } |
| } |
| |
| (*curIdx)++; |
| (*numLogicalIndexes)++; |
| } |
| |
| /* from the hash entry, fetch the defaultPartList */ |
| foreach(lc, entry->defaultPartList) |
| { |
| PartitionIndexNode *node = (PartitionIndexNode *)lfirst(lc); |
| AttrNumber *indexKeys; |
| |
| li->logicalIndexInfo[*curIdx]->partCons = NULL; |
| li->logicalIndexInfo[*curIdx]->logicalIndexOid = entry->logicalIndexOid; |
| li->logicalIndexInfo[*curIdx]->nColumns = entry->ii->ii_NumIndexAttrs; |
| indexKeys = palloc(sizeof(AttrNumber) * entry->ii->ii_NumIndexAttrs); |
| memcpy((char *)indexKeys, |
| &(entry->ii->ii_KeyAttrNumbers), sizeof(AttrNumber) * entry->ii->ii_NumIndexAttrs); |
| li->logicalIndexInfo[*curIdx]->indexKeys = indexKeys; |
| li->logicalIndexInfo[*curIdx]->indExprs = (List *)copyObject(entry->ii->ii_Expressions); |
| li->logicalIndexInfo[*curIdx]->indPred = (List *)copyObject(entry->ii->ii_Predicate); |
| li->logicalIndexInfo[*curIdx]->indIsUnique = entry->ii->ii_Unique; |
| li->logicalIndexInfo[*curIdx]->indType = entry->indType; |
| |
| /* |
| * Default parts don't have constraints, so they cannot be represented |
| * as part of the partial index predicate. |
| * |
| * Instead each index on a default part is represented as a different logical |
| * index. Since we need a way to identify the specific default part, we include |
| * the defaultLevels information, in addition to ANY constraint on the default |
| * part. |
| */ |
| li->logicalIndexInfo[*curIdx]->partCons = getPartConstraints(node->parchildrelid, root, NIL /*partKey*/); |
| |
| /* get the level on which partitioning key is default */ |
| li->logicalIndexInfo[*curIdx]->defaultLevels = list_copy(node->defaultLevels); |
| (*curIdx)++; |
| (*numLogicalIndexes)++; |
| } |
| } |
| |
| /* |
| * createPartsIndexResult |
| * Populate the result array of logical indexes. Most of the information needed |
| * is in the IndexInfoHash. The partial index predicate needs to be generated |
| * by a call to generateLogicalIndexPred. |
| */ |
| static LogicalIndexes * |
| createPartsIndexResult(Oid root) |
| { |
| HASH_SEQ_STATUS hash_seq; |
| int numResultRows = 0; |
| LogicalIndexes *li; |
| LogicalIndexInfoHashEntry *entry; |
| |
| hash_seq_init(&hash_seq, LogicalIndexInfoHash); |
| |
| /* |
| * we allocate space for twice the number of logical indexes. |
| * this is because, every index on a default part will be |
| * returned as a separate index. |
| */ |
| numResultRows = numLogicalIndexes + numIndexesOnDefaultParts; |
| |
| li = (LogicalIndexes *) palloc0(sizeof(LogicalIndexes)); |
| |
| li->numLogicalIndexes = 0; |
| li->logicalIndexInfo = NULL; |
| |
| li->logicalIndexInfo = (LogicalIndexInfo **)palloc0(numResultRows * sizeof(LogicalIndexInfo *)); |
| |
| /* should we limit the amount of memory being allocated here ? */ |
| for (int i=0; i < numResultRows; i++) |
| li->logicalIndexInfo[i] = (LogicalIndexInfo *)palloc0(sizeof(LogicalIndexInfo)); |
| |
| int curIdx = 0; |
| |
| /* for each logical index, get the part constraints as partial index predicates */ |
| while ((entry = hash_seq_search(&hash_seq))) |
| { |
| generateLogicalIndexPred(li, &curIdx, entry, root, &li->numLogicalIndexes); |
| } |
| |
| return li; |
| } |
| |
| /* |
| * getPhysicalIndexRelid |
| * This function returns the physical index relid corresponding |
| * to the index described by the logicalIndexInfo input structure |
| * in the relation identified by the partOid. |
| * The 1st matching index is returned. |
| * |
| * NOTE: index attnums in the provided logicalIndexInfo input structure |
| * have to be mapped to the root. |
| */ |
| Oid |
| getPhysicalIndexRelid(LogicalIndexInfo *iInfo, Oid partOid) |
| { |
| HeapTuple tup; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| Oid resultOid = InvalidOid; |
| IndexInfo *ii = NULL; |
| Relation indexRelation; |
| bool indKeyEqual; |
| |
| indexRelation = heap_open(IndexRelationId, AccessShareLock); |
| |
| /* now look up pg_index using indrelid */ |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), indexRelation), |
| cql("SELECT * FROM pg_index " |
| " WHERE indrelid = :1 ", |
| ObjectIdGetDatum(partOid))); |
| |
| /* fetch tuples */ |
| while (HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| Form_pg_index indForm = (Form_pg_index) GETSTRUCT(tup); |
| |
| if (NULL != ii) |
| { |
| pfree(ii); |
| ii = NULL; |
| } |
| |
| ii = populateIndexInfo(pcqCtx, tup, indForm); |
| |
| if (ii->ii_NumIndexAttrs != iInfo->nColumns) |
| continue; |
| |
| /* compare indKey */ |
| indKeyEqual = true; |
| for (int i = 0; i < ii->ii_NumIndexAttrs; i++) |
| { |
| if (ii->ii_KeyAttrNumbers[i] != iInfo->indexKeys[i]) |
| { |
| indKeyEqual = false; |
| break; |
| } |
| } |
| |
| if (!indKeyEqual) |
| continue; |
| |
| /* compare uniqueness attribute */ |
| if (iInfo->indIsUnique != ii->ii_Unique) |
| continue; |
| |
| /* compare indPred */ |
| if (!equal(iInfo->indPred, ii->ii_Predicate)) |
| continue; |
| |
| /* compare indExprs */ |
| if (equal(iInfo->indExprs, ii->ii_Expressions)) |
| { |
| /* found matching index */ |
| resultOid = indForm->indexrelid; |
| break; |
| } |
| |
| // TODO: antova - Mar 28, 2014; compare if this is a bitmap index |
| } |
| |
| if (NULL != ii) |
| { |
| pfree(ii); |
| ii = NULL; |
| } |
| |
| caql_endscan(pcqCtx); |
| heap_close(indexRelation, AccessShareLock); |
| |
| return resultOid; |
| } |
| |
| /* |
| * dumpPartsIndexInfo |
| * print debugging info |
| */ |
| static void |
| dumpPartsIndexInfo(PartitionIndexNode *n, int level) |
| { |
| ListCell *lc; |
| StringInfoData logicalIndexes; |
| initStringInfo(&logicalIndexes); |
| |
| if (n) |
| { |
| for (int i = 0; i <= level; i++) |
| appendStringInfo(&logicalIndexes, "%s", " "); |
| |
| appendStringInfo(&logicalIndexes, "%d ", n->parchildrelid); |
| |
| int x; |
| x = bms_first_from(n->index, 0); |
| appendStringInfo(&logicalIndexes, "%s ", " ("); |
| |
| while (x >= 0) |
| { |
| appendStringInfo(&logicalIndexes, "%d ", x); |
| x = bms_first_from(n->index, x + 1); |
| } |
| |
| appendStringInfo(&logicalIndexes, "%s ", " )"); |
| |
| elog(DEBUG5, "%s", logicalIndexes.data); |
| } |
| |
| |
| if (n->children) |
| { |
| foreach (lc, n->children) |
| { |
| PartitionIndexNode *child = (PartitionIndexNode *) lfirst(lc); |
| dumpPartsIndexInfo(child, level+1); |
| appendStringInfo(&logicalIndexes, " "); |
| } |
| } |
| } |
| |
| /* |
| * mergeIntervals |
| * merge the two intervals by creating a disjunction of the given intervals, or collapsing |
| * them into one interval when possible |
| * |
| * This function's arguments represent two range constraints, which the function attempts |
| * to collapse into one if they share a common boundary. If no collapse is possible, |
| * the function returns a disjunction of the two intervals. |
| */ |
| static Node * |
| mergeIntervals(Node *intervalFst, Node *intervalSnd) |
| { |
| Node *pnodeStart1 = NULL; |
| Node *pnodeEnd1 = NULL; |
| Node *pnodeStart2 = NULL; |
| Node *pnodeEnd2 = NULL; |
| |
| extractStartEndRange(intervalFst, &pnodeStart1, &pnodeEnd1); |
| extractStartEndRange(intervalSnd, &pnodeStart2, &pnodeEnd2); |
| |
| if (NULL != pnodeEnd1 && NULL != pnodeStart2) |
| { |
| OpExpr *opexprEnd1 = (OpExpr *) pnodeEnd1; |
| OpExpr *opexprStart2 = (OpExpr *) pnodeStart2; |
| Var *pvarEnd1 = NULL; |
| Var *pvarStart2 = NULL; |
| Const *pconstEnd1 = NULL; |
| Const *pconstStart2 = NULL; |
| Oid opnoEnd1 = InvalidOid; |
| Oid opnoStart2 = InvalidOid; |
| |
| /* extract boundaries of the two intervals */ |
| extractOpExprComponents(opexprEnd1, &pvarEnd1, &pconstEnd1, &opnoEnd1); |
| extractOpExprComponents(opexprStart2, &pvarStart2, &pconstStart2, &opnoStart2); |
| if (InvalidOid != opnoEnd1 && InvalidOid != opnoStart2 && |
| equal(pvarEnd1, pvarStart2) && equal(pconstEnd1, pconstStart2) && /* middle point is the same */ |
| opnoEnd1 == get_negator(opnoStart2) /* this guaranteed that the middle point is included in exactly 1 of the intervals */ |
| ) |
| { |
| if (NULL != pnodeStart1 && NULL != pnodeEnd2) |
| { |
| /* merge intervals of the form (x,y), [y, z) into (x,z) */ |
| return (Node *) make_andclause(list_make2(pnodeStart1, pnodeEnd2)); |
| } |
| |
| if (NULL != pnodeStart1) |
| { |
| /* merge intervals of the form (x,y), [y, inf) into (x, inf) */ |
| Var *pvarStart1 = NULL; |
| Const *pconstStart1 = NULL; |
| Oid opnoStart1 = InvalidOid; |
| OpExpr *opexprStart1 = (OpExpr *) pnodeStart1; |
| extractOpExprComponents(opexprStart1, &pvarStart1, &pconstStart1, &opnoStart1); |
| |
| return (Node *) make_opclause(opnoStart1, opexprStart1->opresulttype, opexprStart1->opretset, (Expr *) pvarStart1, (Expr *) pconstStart1); |
| } |
| |
| if (NULL != pnodeEnd2) |
| { |
| /* merge intervals of the form (-inf,x), [x, y) into (-inf, y) */ |
| Var *pvarEnd2 = NULL; |
| Const *pconstEnd2 = NULL; |
| Oid opnoEnd2 = InvalidOid; |
| OpExpr *opexprEnd2 = (OpExpr *) pnodeEnd2; |
| |
| extractOpExprComponents(opexprEnd2, &pvarEnd2, &pconstEnd2, &opnoEnd2); |
| |
| return (Node *) make_opclause(opnoEnd2, opexprEnd2->opresulttype, opexprEnd2->opretset, (Expr *) pvarEnd2, (Expr *) pconstEnd2); |
| } |
| |
| Assert(NULL == pnodeStart1 && NULL == pnodeEnd2); |
| |
| /* merge (-inf, x), [x,inf) into true */ |
| |
| return (Node *) makeBoolConst(true /*value*/, false /*isnull*/); |
| } |
| } |
| return (Node *) make_orclause(list_make2(intervalFst, intervalSnd)); |
| } |
| |
| /* |
| * extractStartEndRange |
| * Given an expression representing an interval constraint, extract the expressions defining the interval's start and end |
| */ |
| static void extractStartEndRange(Node *clause, Node **ppnodeStart, Node **ppnodeEnd) |
| { |
| if (IsA(clause, OpExpr)) |
| { |
| OpExpr *opexpr = (OpExpr *) clause; |
| Expr *pexprLeft = list_nth(opexpr->args, 0); |
| Expr *pexprRight = list_nth(opexpr->args, 1); |
| CmpType cmptype = get_comparison_type(opexpr->opno, exprType((Node *) pexprLeft), exprType((Node *) pexprRight)); |
| |
| if (CmptEq == cmptype) |
| { |
| *ppnodeStart = *ppnodeEnd = clause; |
| return; |
| } |
| |
| if ((CmptLEq == cmptype || CmptLT == cmptype) && IsA(pexprRight, Const)) |
| { |
| /* op expr of the form pk <= Const or pk < Const */ |
| *ppnodeStart = NULL; |
| *ppnodeEnd = clause; |
| return; |
| } |
| |
| if ((CmptGEq == cmptype || CmptGT == cmptype) && IsA(pexprRight, Const)) |
| { |
| /* op expr of the form pk >= Const or pk > Const */ |
| *ppnodeEnd = NULL; |
| *ppnodeStart = clause; |
| return; |
| } |
| } |
| |
| /* only expressions of the form x > C1 and x < C2 supported: ignore all other expression types */ |
| |
| if (!IsA(clause, BoolExpr) || |
| AND_EXPR != ((BoolExpr *) clause)->boolop || |
| 2 < list_length(((BoolExpr *) clause)->args)) |
| { |
| return; |
| } |
| |
| BoolExpr *bool_expr = (BoolExpr *) clause; |
| |
| Node *nodeStart = list_nth(bool_expr->args, 0); |
| Node *nodeEnd = list_nth(bool_expr->args, 1); |
| |
| if (!IsA(nodeStart, OpExpr) || !IsA(nodeEnd, OpExpr)) |
| { |
| return; |
| } |
| |
| OpExpr *opexprStart = (OpExpr *) nodeStart; |
| OpExpr *opexprEnd = (OpExpr *) nodeEnd; |
| |
| CmpType cmptLeft = get_comparison_type(opexprStart->opno, exprType(list_nth(opexprStart->args, 0)), exprType(list_nth(opexprStart->args, 1))); |
| CmpType cmptRight = get_comparison_type(opexprEnd->opno, exprType(list_nth(opexprEnd->args, 0)), exprType(list_nth(opexprEnd->args, 1))); |
| |
| if ((CmptGT != cmptLeft && CmptGEq != cmptLeft) || (CmptLT != cmptRight && CmptLEq != cmptRight)) |
| { |
| return; |
| } |
| |
| *ppnodeStart = nodeStart; |
| *ppnodeEnd = nodeEnd; |
| } |
| |
| /* |
| * extractOpExprComponents |
| * for an opexpr of type Var op Const, extract its components |
| */ |
| static void extractOpExprComponents(OpExpr *opexpr, Var **ppvar, Const **ppconst, Oid *opno) |
| { |
| Node *pnodeLeft = list_nth(opexpr->args, 0); |
| Node *pnodeRight = list_nth(opexpr->args, 1); |
| if (IsA(pnodeLeft, Var) && IsA(pnodeRight, Const)) |
| { |
| *ppvar = (Var *) pnodeLeft; |
| *ppconst = (Const *) pnodeRight; |
| *opno = opexpr->opno; |
| } |
| else if (IsA(pnodeLeft, Const) && IsA(pnodeRight, Var) && InvalidOid != get_commutator(opexpr->opno)) |
| { |
| *ppvar = (Var *) pnodeRight; |
| *ppconst = (Const *) pnodeLeft; |
| *opno = get_commutator(opexpr->opno); |
| } |
| } |
| |
| |
| /* |
| * LogicalIndexInfoForIndexOid |
| * construct the logical index info for a given index oid |
| */ |
| LogicalIndexInfo *logicalIndexInfoForIndexOid(Oid rootOid, Oid indexOid) |
| { |
| /* fetch index from catalog */ |
| Relation indexRelation = heap_open(IndexRelationId, AccessShareLock); |
| |
| cqContext cqc; |
| cqContext *pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), indexRelation), |
| cql("SELECT * FROM pg_index " |
| " WHERE indexrelid = :1 ", |
| ObjectIdGetDatum(indexOid))); |
| |
| HeapTuple tup = NULL; |
| |
| if (!HeapTupleIsValid(tup = caql_getnext(pcqCtx))) |
| { |
| caql_endscan(pcqCtx); |
| heap_close(indexRelation, AccessShareLock); |
| elog(ERROR, "Index not found: %u", indexOid); |
| } |
| |
| Form_pg_index indForm = (Form_pg_index) GETSTRUCT(tup); |
| IndexInfo *pindexInfo = populateIndexInfo(pcqCtx, tup, indForm); |
| Oid partOid = indForm->indrelid; |
| |
| caql_endscan(pcqCtx); |
| heap_close(indexRelation, AccessShareLock); |
| |
| // remap attributes |
| Relation rootRel = heap_open(rootOid, AccessShareLock); |
| Relation partRel = heap_open(partOid, AccessShareLock); |
| |
| TupleDesc rootTupDesc = rootRel->rd_att; |
| TupleDesc partTupDesc = partRel->rd_att; |
| |
| char relstorage = partRel->rd_rel->relstorage; |
| AttrNumber *attMap = varattnos_map(rootTupDesc, partTupDesc); |
| heap_close(rootRel, AccessShareLock); |
| heap_close(partRel, AccessShareLock); |
| |
| // populate the index information |
| LogicalIndexInfo *plogicalIndexInfo = (LogicalIndexInfo *) palloc0(sizeof(LogicalIndexInfo)); |
| plogicalIndexInfo->nColumns = pindexInfo->ii_NumIndexAttrs; |
| plogicalIndexInfo->indIsUnique = pindexInfo->ii_Unique; |
| plogicalIndexInfo->logicalIndexOid = indexOid; |
| |
| int numIndexKeys = pindexInfo->ii_NumIndexAttrs; |
| plogicalIndexInfo->indexKeys = palloc(sizeof(AttrNumber) * numIndexKeys); |
| memcpy((char *)plogicalIndexInfo->indexKeys, &(pindexInfo->ii_KeyAttrNumbers), sizeof(AttrNumber) * numIndexKeys); |
| |
| plogicalIndexInfo->indExprs = copyObject(pindexInfo->ii_Expressions); |
| plogicalIndexInfo->indPred = copyObject(pindexInfo->ii_Predicate); |
| |
| /* remap expression fields */ |
| /* map the attrnos if necessary */ |
| if (attMap) |
| { |
| for (int i = 0; i < plogicalIndexInfo->nColumns; i++) |
| { |
| if (plogicalIndexInfo->indexKeys[i] != 0) |
| { |
| plogicalIndexInfo->indexKeys[i] = attMap[(plogicalIndexInfo->indexKeys[i]) - 1]; |
| } |
| } |
| change_varattnos_of_a_node((Node *) plogicalIndexInfo->indExprs, attMap); |
| change_varattnos_of_a_node((Node *) plogicalIndexInfo->indPred, attMap); |
| } |
| |
| plogicalIndexInfo->indType = INDTYPE_BITMAP; |
| if (RELSTORAGE_HEAP == relstorage) |
| { |
| |
| Relation indRel = RelationIdGetRelation(indForm->indexrelid); |
| Assert(NULL != indRel); |
| if (BTREE_AM_OID == indRel->rd_rel->relam) |
| { |
| plogicalIndexInfo->indType = INDTYPE_BTREE; |
| } |
| RelationClose(indRel); |
| } |
| |
| return plogicalIndexInfo; |
| } |