/* * SearchSysCacheExistsAttName * * As above, an attisdropped-aware version of SearchSysCacheExists. */ bool SearchSysCacheExistsAttName(Oid relid, const char *attname) { HeapTuple tuple; tuple = SearchSysCacheAttName(relid, attname); if (!HeapTupleIsValid(tuple)) return false; ReleaseSysCache(tuple); return true; }
/* * SearchSysCacheCopyAttName * * As above, an attisdropped-aware version of SearchSysCacheCopy. */ HeapTuple SearchSysCacheCopyAttName(Oid relid, const char *attname) { HeapTuple tuple, newtuple; tuple = SearchSysCacheAttName(relid, attname); if (!HeapTupleIsValid(tuple)) return tuple; newtuple = heap_copytuple(tuple); ReleaseSysCache(tuple); return newtuple; }
/* ---------------------------------------------------------------- * caql_getattname_scan() * * The equivalent of SearchSysCacheAttName - * caql_beginscan(pCtx, * "SELECT * FROM pg_attribute * WHERE attrelid = :relid * AND attname = :attname * AND attisdropped is false" * ); * * That is, find the existing ("undropped") attribute and return * a context where the tuple is already fetched. Retrieve the tuple * using caql_get_current() * NOTE: this is hideous. My abject apologies. * NOTE: need to be careful if this pCtx is used for update... * ---------------------------------------------------------------- */ cqContext * caql_getattname_scan(cqContext *pCtx0, Oid relid, const char *attname) { cqContext *pCtx; HeapTuple tup; disable_attribute_check(relid); /* use the provided context, or *allocate* a clean one */ if (pCtx0) pCtx = pCtx0; else { pCtx = (cqContext *) palloc0(sizeof(cqContext)); pCtx->cq_free = true; /* free this context in caql_endscan */ } tup = SearchSysCacheAttName(relid, attname); if (pCtx) { /* treat as ATTNAME cache lookup */ pCtx->cq_usesyscache = true; pCtx->cq_cacheId = ATTNAME; pCtx->cq_NumKeys = 2; /* NOTE: no valid relation for subsequent INSERT/UPDATE/DELETE *unless* an external relation is supplied */ if (!pCtx->cq_externrel) { pCtx->cq_externrel = true; /* pretend we have external relation */ pCtx->cq_heap_rel = InvalidRelation; } if (!pCtx->cq_setidxOK) pCtx->cq_useidxOK = true; pCtx->cq_freeScan = true; pCtx->cq_EOF = true; /* for caql_update_current(), etc */ if (pCtx->cq_setpklock) caql_iud_switch(pCtx, 0, tup, NULL, false /* Wait */); pCtx->cq_lasttup = tup; } return pCtx; }
/* * from lsyscach.c/get_attnum(): * * Given the relation id and the attribute name, * return the "attnum" field from the attribute relation. * * Returns InvalidAttrNumber if the attr doesn't exist (or is dropped). */ AttrNumber caql_getattnumber(Oid relid, const char *attname) { HeapTuple tp; tp = SearchSysCacheAttName(relid, attname); if (HeapTupleIsValid(tp)) { Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); AttrNumber result; result = att_tup->attnum; ReleaseSysCache(tp); return result; } else return InvalidAttrNumber; }
/* * DefineIndex * Creates a new index. * * 'heapRelation': the relation the index will apply to. * 'indexRelationName': the name for the new index, or NULL to indicate * that a nonconflicting default name should be picked. * 'indexRelationId': normally InvalidOid, but during bootstrap can be * nonzero to specify a preselected OID for the index. * 'accessMethodName': name of the AM to use. * 'tableSpaceName': name of the tablespace to create the index in. * NULL specifies using the appropriate default. * 'attributeList': a list of IndexElem specifying columns and expressions * to index on. * 'predicate': the partial-index condition, or NULL if none. * 'rangetable': needed to interpret the predicate. * 'options': reloptions from WITH (in list-of-DefElem form). * 'unique': make the index enforce uniqueness. * 'primary': mark the index as a primary key in the catalogs. * 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint, * so build a pg_constraint entry for it. * 'is_alter_table': this is due to an ALTER rather than a CREATE operation. * 'check_rights': check for CREATE rights in the namespace. (This should * be true except when ALTER is deleting/recreating an index.) * 'skip_build': make the catalog entries but leave the index file empty; * it will be filled later. * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints. * 'concurrent': avoid blocking writers to the table while building. */ void DefineIndex(RangeVar *heapRelation, char *indexRelationName, Oid indexRelationId, char *accessMethodName, char *tableSpaceName, List *attributeList, Expr *predicate, List *rangetable, List *options, bool unique, bool primary, bool isconstraint, bool is_alter_table, bool check_rights, bool skip_build, bool quiet, bool concurrent) { Oid *classObjectId; Oid accessMethodId; Oid relationId; Oid namespaceId; Oid tablespaceId; Relation rel; HeapTuple tuple; Form_pg_am accessMethodForm; RegProcedure amoptions; Datum reloptions; IndexInfo *indexInfo; int numberOfAttributes; List *old_xact_list; ListCell *lc; uint32 ixcnt; LockRelId heaprelid; LOCKTAG heaplocktag; Snapshot snapshot; Relation pg_index; HeapTuple indexTuple; Form_pg_index indexForm; /* * count attributes in index */ numberOfAttributes = list_length(attributeList); if (numberOfAttributes <= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("must specify at least one column"))); if (numberOfAttributes > INDEX_MAX_KEYS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("cannot use more than %d columns in an index", INDEX_MAX_KEYS))); /* * Open heap relation, acquire a suitable lock on it, remember its OID * * Only SELECT ... FOR UPDATE/SHARE are allowed while doing a standard * index build; but for concurrent builds we allow INSERT/UPDATE/DELETE * (but not VACUUM). */ rel = heap_openrv(heapRelation, (concurrent ? ShareUpdateExclusiveLock : ShareLock)); relationId = RelationGetRelid(rel); namespaceId = RelationGetNamespace(rel); /* Note: during bootstrap may see uncataloged relation */ if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_UNCATALOGED) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", heapRelation->relname))); /* * Don't try to CREATE INDEX on temp tables of other backends. */ if (isOtherTempNamespace(namespaceId)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create indexes on temporary tables of other sessions"))); /* * Verify we (still) have CREATE rights in the rel's namespace. * (Presumably we did when the rel was created, but maybe not anymore.) * Skip check if caller doesn't want it. Also skip check if * bootstrapping, since permissions machinery may not be working yet. */ if (check_rights && !IsBootstrapProcessingMode()) { AclResult aclresult; aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); } /* * Select tablespace to use. If not specified, use default_tablespace * (which may in turn default to database's default). */ if (tableSpaceName) { tablespaceId = get_tablespace_oid(tableSpaceName); if (!OidIsValid(tablespaceId)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("tablespace \"%s\" does not exist", tableSpaceName))); } else { tablespaceId = GetDefaultTablespace(); /* note InvalidOid is OK in this case */ } /* Check permissions except when using database's default */ if (OidIsValid(tablespaceId)) { AclResult aclresult; aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceId)); } /* * Force shared indexes into the pg_global tablespace. This is a bit of a * hack but seems simpler than marking them in the BKI commands. */ if (rel->rd_rel->relisshared) tablespaceId = GLOBALTABLESPACE_OID; /* * Select name for index if caller didn't specify */ if (indexRelationName == NULL) { if (primary) indexRelationName = ChooseRelationName(RelationGetRelationName(rel), NULL, "pkey", namespaceId); else { IndexElem *iparam = (IndexElem *) linitial(attributeList); indexRelationName = ChooseRelationName(RelationGetRelationName(rel), iparam->name, "key", namespaceId); } } /* * look up the access method, verify it can handle the requested features */ tuple = SearchSysCache(AMNAME, PointerGetDatum(accessMethodName), 0, 0, 0); if (!HeapTupleIsValid(tuple)) { /* * Hack to provide more-or-less-transparent updating of old RTREE * indexes to GIST: if RTREE is requested and not found, use GIST. */ if (strcmp(accessMethodName, "rtree") == 0) { ereport(NOTICE, (errmsg("substituting access method \"gist\" for obsolete method \"rtree\""))); accessMethodName = "gist"; tuple = SearchSysCache(AMNAME, PointerGetDatum(accessMethodName), 0, 0, 0); } if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("access method \"%s\" does not exist", accessMethodName))); } accessMethodId = HeapTupleGetOid(tuple); accessMethodForm = (Form_pg_am) GETSTRUCT(tuple); if (unique && !accessMethodForm->amcanunique) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("access method \"%s\" does not support unique indexes", accessMethodName))); if (numberOfAttributes > 1 && !accessMethodForm->amcanmulticol) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("access method \"%s\" does not support multicolumn indexes", accessMethodName))); amoptions = accessMethodForm->amoptions; ReleaseSysCache(tuple); /* * If a range table was created then check that only the base rel is * mentioned. */ if (rangetable != NIL) { if (list_length(rangetable) != 1 || getrelid(1, rangetable) != relationId) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("index expressions and predicates may refer only to the table being indexed"))); } /* * Validate predicate, if given */ if (predicate) CheckPredicate(predicate); /* * Extra checks when creating a PRIMARY KEY index. */ if (primary) { List *cmds; ListCell *keys; /* * If ALTER TABLE, check that there isn't already a PRIMARY KEY. In * CREATE TABLE, we have faith that the parser rejected multiple pkey * clauses; and CREATE INDEX doesn't have a way to say PRIMARY KEY, so * it's no problem either. */ if (is_alter_table && relationHasPrimaryKey(rel)) { ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("multiple primary keys for table \"%s\" are not allowed", RelationGetRelationName(rel)))); } /* * Check that all of the attributes in a primary key are marked as not * null, otherwise attempt to ALTER TABLE .. SET NOT NULL */ cmds = NIL; foreach(keys, attributeList) { IndexElem *key = (IndexElem *) lfirst(keys); HeapTuple atttuple; if (!key->name) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("primary keys cannot be expressions"))); /* System attributes are never null, so no problem */ if (SystemAttributeByName(key->name, rel->rd_rel->relhasoids)) continue; atttuple = SearchSysCacheAttName(relationId, key->name); if (HeapTupleIsValid(atttuple)) { if (!((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull) { /* Add a subcommand to make this one NOT NULL */ AlterTableCmd *cmd = makeNode(AlterTableCmd); cmd->subtype = AT_SetNotNull; cmd->name = key->name; cmds = lappend(cmds, cmd); } ReleaseSysCache(atttuple); } else { /* * This shouldn't happen during CREATE TABLE, but can happen * during ALTER TABLE. Keep message in sync with * transformIndexConstraints() in parser/analyze.c. */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" named in key does not exist", key->name))); } } /* * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade to child * tables? Currently, since the PRIMARY KEY itself doesn't cascade, * we don't cascade the notnull constraint(s) either; but this is * pretty debatable. * * XXX: possible future improvement: when being called from ALTER * TABLE, it would be more efficient to merge this with the outer * ALTER TABLE, so as to avoid two scans. But that seems to * complicate DefineIndex's API unduly. */ if (cmds) AlterTableInternal(relationId, cmds, false); }