1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
2000-01-12 06:04:42 +01:00
|
|
|
* indexcmds.c
|
2001-07-17 23:53:01 +02:00
|
|
|
* POSTGRES define and remove index code.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2007-01-05 23:20:05 +01:00
|
|
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2007-01-05 23:20:05 +01:00
|
|
|
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.151 2007/01/05 22:19:26 momjian Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1996-11-04 00:57:43 +01:00
|
|
|
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "postgres.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2006-02-10 20:01:12 +01:00
|
|
|
#include "access/genam.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "access/heapam.h"
|
2006-07-04 00:45:41 +02:00
|
|
|
#include "access/reloptions.h"
|
2006-08-25 06:06:58 +02:00
|
|
|
#include "access/transam.h"
|
2006-07-13 18:49:20 +02:00
|
|
|
#include "access/xact.h"
|
2000-07-04 08:11:54 +02:00
|
|
|
#include "catalog/catalog.h"
|
2002-07-12 20:43:19 +02:00
|
|
|
#include "catalog/dependency.h"
|
2003-01-02 20:29:22 +01:00
|
|
|
#include "catalog/heap.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "catalog/index.h"
|
2006-02-10 20:01:12 +01:00
|
|
|
#include "catalog/indexing.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "catalog/pg_opclass.h"
|
2004-11-05 20:17:13 +01:00
|
|
|
#include "catalog/pg_tablespace.h"
|
2003-06-27 16:45:32 +02:00
|
|
|
#include "commands/dbcommands.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "commands/defrem.h"
|
2003-01-02 20:29:22 +01:00
|
|
|
#include "commands/tablecmds.h"
|
2004-06-18 08:14:31 +02:00
|
|
|
#include "commands/tablespace.h"
|
2004-06-10 19:56:03 +02:00
|
|
|
#include "mb/pg_wchar.h"
|
2000-07-04 08:11:54 +02:00
|
|
|
#include "miscadmin.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "optimizer/clauses.h"
|
2000-04-25 04:45:54 +02:00
|
|
|
#include "parser/parse_coerce.h"
|
2003-05-28 18:04:02 +02:00
|
|
|
#include "parser/parse_expr.h"
|
2000-02-25 03:58:48 +01:00
|
|
|
#include "parser/parse_func.h"
|
2006-07-11 19:04:13 +02:00
|
|
|
#include "parser/parsetree.h"
|
2002-04-27 05:45:03 +02:00
|
|
|
#include "utils/acl.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "utils/builtins.h"
|
2006-02-10 20:01:12 +01:00
|
|
|
#include "utils/fmgroids.h"
|
2001-07-17 23:53:01 +02:00
|
|
|
#include "utils/lsyscache.h"
|
2005-05-06 19:24:55 +02:00
|
|
|
#include "utils/memutils.h"
|
2004-05-05 06:48:48 +02:00
|
|
|
#include "utils/relcache.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "utils/syscache.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2001-07-17 23:53:01 +02:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/* non-export function prototypes */
|
2003-12-28 22:57:37 +01:00
|
|
|
static void CheckPredicate(Expr *predicate);
|
2003-05-28 18:04:02 +02:00
|
|
|
static void ComputeIndexAttrs(IndexInfo *indexInfo, Oid *classOidP,
|
2004-08-29 07:07:03 +02:00
|
|
|
List *attList,
|
|
|
|
Oid relId,
|
|
|
|
char *accessMethodName, Oid accessMethodId,
|
|
|
|
bool isconstraint);
|
2003-05-28 18:04:02 +02:00
|
|
|
static Oid GetIndexOpClass(List *opclass, Oid attrType,
|
2003-08-04 02:43:34 +02:00
|
|
|
char *accessMethodName, Oid accessMethodId);
|
2004-05-05 06:48:48 +02:00
|
|
|
static bool relationHasPrimaryKey(Relation rel);
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* DefineIndex
|
1997-09-07 07:04:48 +02:00
|
|
|
* Creates a new index.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2004-05-05 06:48:48 +02:00
|
|
|
* '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.
|
2005-04-14 03:38:22 +02:00
|
|
|
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
|
|
|
|
* nonzero to specify a preselected OID for the index.
|
2004-05-05 06:48:48 +02:00
|
|
|
* 'accessMethodName': name of the AM to use.
|
2004-06-18 08:14:31 +02:00
|
|
|
* 'tableSpaceName': name of the tablespace to create the index in.
|
2004-11-05 20:17:13 +01:00
|
|
|
* NULL specifies using the appropriate default.
|
2004-05-05 06:48:48 +02:00
|
|
|
* 'attributeList': a list of IndexElem specifying columns and expressions
|
2003-05-28 18:04:02 +02:00
|
|
|
* to index on.
|
2004-05-05 06:48:48 +02:00
|
|
|
* 'predicate': the partial-index condition, or NULL if none.
|
|
|
|
* 'rangetable': needed to interpret the predicate.
|
2006-07-04 00:45:41 +02:00
|
|
|
* 'options': reloptions from WITH (in list-of-DefElem form).
|
2004-05-05 06:48:48 +02:00
|
|
|
* '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.
|
2006-08-25 06:06:58 +02:00
|
|
|
* 'concurrent': avoid blocking writers to the table while building.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
2002-03-26 20:17:02 +01:00
|
|
|
DefineIndex(RangeVar *heapRelation,
|
1997-09-07 07:04:48 +02:00
|
|
|
char *indexRelationName,
|
2005-04-14 03:38:22 +02:00
|
|
|
Oid indexRelationId,
|
1997-09-07 07:04:48 +02:00
|
|
|
char *accessMethodName,
|
2004-06-18 08:14:31 +02:00
|
|
|
char *tableSpaceName,
|
1997-09-08 23:56:23 +02:00
|
|
|
List *attributeList,
|
2004-05-05 06:48:48 +02:00
|
|
|
Expr *predicate,
|
|
|
|
List *rangetable,
|
2006-07-02 04:23:23 +02:00
|
|
|
List *options,
|
1997-09-07 07:04:48 +02:00
|
|
|
bool unique,
|
1999-01-21 23:48:20 +01:00
|
|
|
bool primary,
|
2002-07-12 20:43:19 +02:00
|
|
|
bool isconstraint,
|
2004-05-05 06:48:48 +02:00
|
|
|
bool is_alter_table,
|
|
|
|
bool check_rights,
|
|
|
|
bool skip_build,
|
2006-08-25 06:06:58 +02:00
|
|
|
bool quiet,
|
|
|
|
bool concurrent)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Oid *classObjectId;
|
|
|
|
Oid accessMethodId;
|
|
|
|
Oid relationId;
|
2002-04-27 05:45:03 +02:00
|
|
|
Oid namespaceId;
|
2004-06-18 08:14:31 +02:00
|
|
|
Oid tablespaceId;
|
2002-01-04 00:21:32 +01:00
|
|
|
Relation rel;
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
HeapTuple tuple;
|
|
|
|
Form_pg_am accessMethodForm;
|
2006-07-04 00:45:41 +02:00
|
|
|
RegProcedure amoptions;
|
|
|
|
Datum reloptions;
|
2000-07-15 00:18:02 +02:00
|
|
|
IndexInfo *indexInfo;
|
1997-09-08 04:41:22 +02:00
|
|
|
int numberOfAttributes;
|
2006-08-27 21:14:34 +02:00
|
|
|
List *old_xact_list;
|
|
|
|
ListCell *lc;
|
2006-08-25 06:06:58 +02:00
|
|
|
uint32 ixcnt;
|
|
|
|
LockRelId heaprelid;
|
2006-08-27 21:14:34 +02:00
|
|
|
LOCKTAG heaplocktag;
|
2006-08-25 06:06:58 +02:00
|
|
|
Snapshot snapshot;
|
2006-10-04 02:30:14 +02:00
|
|
|
Relation pg_index;
|
|
|
|
HeapTuple indexTuple;
|
2006-08-25 06:06:58 +02:00
|
|
|
Form_pg_index indexForm;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
2000-07-15 00:18:02 +02:00
|
|
|
* count attributes in index
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-05-26 06:41:50 +02:00
|
|
|
numberOfAttributes = list_length(attributeList);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (numberOfAttributes <= 0)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("must specify at least one column")));
|
2000-01-12 06:04:42 +01:00
|
|
|
if (numberOfAttributes > INDEX_MAX_KEYS)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("cannot use more than %d columns in an index",
|
2003-07-20 23:56:35 +02:00
|
|
|
INDEX_MAX_KEYS)));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
2002-01-04 00:21:32 +01:00
|
|
|
* Open heap relation, acquire a suitable lock on it, remember its OID
|
2006-08-25 06:06:58 +02:00
|
|
|
*
|
|
|
|
* 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).
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2006-08-25 06:06:58 +02:00
|
|
|
rel = heap_openrv(heapRelation,
|
|
|
|
(concurrent ? ShareUpdateExclusiveLock : ShareLock));
|
|
|
|
|
|
|
|
relationId = RelationGetRelid(rel);
|
|
|
|
namespaceId = RelationGetNamespace(rel);
|
2002-01-04 00:21:32 +01:00
|
|
|
|
|
|
|
/* Note: during bootstrap may see uncataloged relation */
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION &&
|
|
|
|
rel->rd_rel->relkind != RELKIND_UNCATALOGED)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-09-29 18:37:29 +02:00
|
|
|
errmsg("\"%s\" is not a table",
|
2003-07-20 23:56:35 +02:00
|
|
|
heapRelation->relname)));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2006-08-25 06:06:58 +02:00
|
|
|
/*
|
|
|
|
* 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")));
|
2002-01-04 00:21:32 +01:00
|
|
|
|
2002-04-27 05:45:03 +02:00
|
|
|
/*
|
|
|
|
* Verify we (still) have CREATE rights in the rel's namespace.
|
2005-10-15 04:49:52 +02:00
|
|
|
* (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.
|
2002-04-27 05:45:03 +02:00
|
|
|
*/
|
2004-05-05 06:48:48 +02:00
|
|
|
if (check_rights && !IsBootstrapProcessingMode())
|
2002-04-27 05:45:03 +02:00
|
|
|
{
|
|
|
|
AclResult aclresult;
|
|
|
|
|
|
|
|
aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
|
|
|
|
ACL_CREATE);
|
|
|
|
if (aclresult != ACLCHECK_OK)
|
2003-08-01 02:15:26 +02:00
|
|
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
|
|
|
get_namespace_name(namespaceId));
|
2002-04-27 05:45:03 +02:00
|
|
|
}
|
|
|
|
|
2004-11-05 20:17:13 +01:00
|
|
|
/*
|
|
|
|
* Select tablespace to use. If not specified, use default_tablespace
|
|
|
|
* (which may in turn default to database's default).
|
|
|
|
*/
|
2004-06-18 08:14:31 +02:00
|
|
|
if (tableSpaceName)
|
|
|
|
{
|
|
|
|
tablespaceId = get_tablespace_oid(tableSpaceName);
|
|
|
|
if (!OidIsValid(tablespaceId))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("tablespace \"%s\" does not exist",
|
|
|
|
tableSpaceName)));
|
2004-11-05 20:17:13 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tablespaceId = GetDefaultTablespace();
|
|
|
|
/* note InvalidOid is OK in this case */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check permissions except when using database's default */
|
|
|
|
if (OidIsValid(tablespaceId))
|
|
|
|
{
|
|
|
|
AclResult aclresult;
|
|
|
|
|
2004-06-18 08:14:31 +02:00
|
|
|
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
|
|
|
|
ACL_CREATE);
|
|
|
|
if (aclresult != ACLCHECK_OK)
|
|
|
|
aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
|
2004-11-05 20:17:13 +01:00
|
|
|
get_tablespace_name(tablespaceId));
|
2004-06-18 08:14:31 +02:00
|
|
|
}
|
|
|
|
|
2004-11-05 20:17:13 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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.
|
2004-11-05 20:17:13 +01:00
|
|
|
*/
|
|
|
|
if (rel->rd_rel->relisshared)
|
|
|
|
tablespaceId = GLOBALTABLESPACE_OID;
|
|
|
|
|
2004-05-05 06:48:48 +02:00
|
|
|
/*
|
|
|
|
* Select name for index if caller didn't specify
|
|
|
|
*/
|
|
|
|
if (indexRelationName == NULL)
|
|
|
|
{
|
|
|
|
if (primary)
|
2004-06-10 19:56:03 +02:00
|
|
|
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
|
|
|
|
NULL,
|
|
|
|
"pkey",
|
|
|
|
namespaceId);
|
2004-05-05 06:48:48 +02:00
|
|
|
else
|
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
IndexElem *iparam = (IndexElem *) linitial(attributeList);
|
2004-05-05 06:48:48 +02:00
|
|
|
|
2004-06-10 19:56:03 +02:00
|
|
|
indexRelationName = ChooseRelationName(RelationGetRelationName(rel),
|
|
|
|
iparam->name,
|
|
|
|
"key",
|
|
|
|
namespaceId);
|
2004-05-05 06:48:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* look up the access method, verify it can handle the requested features
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
tuple = SearchSysCache(AMNAME,
|
|
|
|
PointerGetDatum(accessMethodName),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
2005-11-07 18:36:47 +01:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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)));
|
|
|
|
}
|
2002-07-20 07:16:59 +02:00
|
|
|
accessMethodId = HeapTupleGetOid(tuple);
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
accessMethodForm = (Form_pg_am) GETSTRUCT(tuple);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
if (unique && !accessMethodForm->amcanunique)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("access method \"%s\" does not support unique indexes",
|
|
|
|
accessMethodName)));
|
2001-10-25 07:50:21 +02:00
|
|
|
if (numberOfAttributes > 1 && !accessMethodForm->amcanmulticol)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("access method \"%s\" does not support multicolumn indexes",
|
|
|
|
accessMethodName)));
|
2000-07-15 00:18:02 +02:00
|
|
|
|
2006-07-04 00:45:41 +02:00
|
|
|
amoptions = accessMethodForm->amoptions;
|
|
|
|
|
Restructure index AM interface for index building and index tuple deletion,
per previous discussion on pghackers. Most of the duplicate code in
different AMs' ambuild routines has been moved out to a common routine
in index.c; this means that all index types now do the right things about
inserting recently-dead tuples, etc. (I also removed support for EXTEND
INDEX in the ambuild routines, since that's about to go away anyway, and
it cluttered the code a lot.) The retail indextuple deletion routines have
been replaced by a "bulk delete" routine in which the indexscan is inside
the access method. I haven't pushed this change as far as it should go yet,
but it should allow considerable simplification of the internal bookkeeping
for deletions. Also, add flag columns to pg_am to eliminate various
hardcoded tests on AM OIDs, and remove unused pg_am columns.
Fix rtree and gist index types to not attempt to store NULLs; before this,
gist usually crashed, while rtree managed not to crash but computed wacko
bounding boxes for NULL entries (which might have had something to do with
the performance problems we've heard about occasionally).
Add AtEOXact routines to hash, rtree, and gist, all of which have static
state that needs to be reset after an error. We discovered this need long
ago for btree, but missed the other guys.
Oh, one more thing: concurrent VACUUM is now the default.
2001-07-16 00:48:19 +02:00
|
|
|
ReleaseSysCache(tuple);
|
2000-07-15 00:18:02 +02:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
/*
|
|
|
|
* If a range table was created then check that only the base rel is
|
|
|
|
* mentioned.
|
|
|
|
*/
|
|
|
|
if (rangetable != NIL)
|
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
if (list_length(rangetable) != 1 || getrelid(1, rangetable) != relationId)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
2003-09-26 17:27:37 +02:00
|
|
|
errmsg("index expressions and predicates may refer only to the table being indexed")));
|
2003-05-28 18:04:02 +02:00
|
|
|
}
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
2003-12-28 22:57:37 +01:00
|
|
|
* Validate predicate, if given
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2002-12-12 16:49:42 +01:00
|
|
|
if (predicate)
|
2003-12-28 22:57:37 +01:00
|
|
|
CheckPredicate(predicate);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-01-02 20:29:22 +01:00
|
|
|
/*
|
2004-05-05 06:48:48 +02:00
|
|
|
* Extra checks when creating a PRIMARY KEY index.
|
2003-01-02 20:29:22 +01:00
|
|
|
*/
|
2003-05-28 18:04:02 +02:00
|
|
|
if (primary)
|
2003-01-02 20:29:22 +01:00
|
|
|
{
|
2004-05-05 06:48:48 +02:00
|
|
|
List *cmds;
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *keys;
|
2003-01-02 20:29:22 +01:00
|
|
|
|
2004-05-05 06:48:48 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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.
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
|
|
|
if (is_alter_table &&
|
|
|
|
relationHasPrimaryKey(rel))
|
|
|
|
{
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("multiple primary keys for table \"%s\" are not allowed",
|
|
|
|
RelationGetRelationName(rel))));
|
2004-05-05 06:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that all of the attributes in a primary key are marked as not
|
|
|
|
* null, otherwise attempt to ALTER TABLE .. SET NOT NULL
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
|
|
|
cmds = NIL;
|
2003-01-02 20:29:22 +01:00
|
|
|
foreach(keys, attributeList)
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
IndexElem *key = (IndexElem *) lfirst(keys);
|
2003-01-02 20:29:22 +01:00
|
|
|
HeapTuple atttuple;
|
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
if (!key->name)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("primary keys cannot be expressions")));
|
2003-05-28 18:04:02 +02:00
|
|
|
|
2003-01-02 20:29:22 +01:00
|
|
|
/* 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))
|
|
|
|
{
|
2003-08-04 02:43:34 +02:00
|
|
|
if (!((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull)
|
2003-01-02 20:29:22 +01:00
|
|
|
{
|
2004-05-05 06:48:48 +02:00
|
|
|
/* Add a subcommand to make this one NOT NULL */
|
2004-08-29 07:07:03 +02:00
|
|
|
AlterTableCmd *cmd = makeNode(AlterTableCmd);
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
cmd->subtype = AT_SetNotNull;
|
|
|
|
cmd->name = key->name;
|
|
|
|
|
|
|
|
cmds = lappend(cmds, cmd);
|
2003-01-02 20:29:22 +01:00
|
|
|
}
|
|
|
|
ReleaseSysCache(atttuple);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2004-05-05 06:48:48 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* This shouldn't happen during CREATE TABLE, but can happen
|
|
|
|
* during ALTER TABLE. Keep message in sync with
|
2004-05-05 06:48:48 +02:00
|
|
|
* transformIndexConstraints() in parser/analyze.c.
|
|
|
|
*/
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("column \"%s\" named in key does not exist",
|
|
|
|
key->name)));
|
2003-01-02 20:29:22 +01:00
|
|
|
}
|
|
|
|
}
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
/*
|
2004-08-29 07:07:03 +02:00
|
|
|
* XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade to child
|
2005-10-15 04:49:52 +02:00
|
|
|
* tables? Currently, since the PRIMARY KEY itself doesn't cascade,
|
|
|
|
* we don't cascade the notnull constraint(s) either; but this is
|
|
|
|
* pretty debatable.
|
2004-05-05 06:48:48 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* 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.
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
|
|
|
if (cmds)
|
|
|
|
AlterTableInternal(relationId, cmds, false);
|
2003-01-02 20:29:22 +01:00
|
|
|
}
|
|
|
|
|
2006-07-04 00:45:41 +02:00
|
|
|
/*
|
|
|
|
* Parse AM-specific options, convert to text array form, validate.
|
|
|
|
*/
|
|
|
|
reloptions = transformRelOptions((Datum) 0, options, false, false);
|
|
|
|
|
|
|
|
(void) index_reloptions(amoptions, reloptions, true);
|
|
|
|
|
2000-07-15 00:18:02 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Prepare arguments for index_create, primarily an IndexInfo structure.
|
|
|
|
* Note that ii_Predicate must be in implicit-AND format.
|
2000-07-15 00:18:02 +02:00
|
|
|
*/
|
|
|
|
indexInfo = makeNode(IndexInfo);
|
2003-05-28 18:04:02 +02:00
|
|
|
indexInfo->ii_NumIndexAttrs = numberOfAttributes;
|
2003-08-04 02:43:34 +02:00
|
|
|
indexInfo->ii_Expressions = NIL; /* for now */
|
2003-05-28 18:04:02 +02:00
|
|
|
indexInfo->ii_ExpressionsState = NIL;
|
2003-12-28 22:57:37 +01:00
|
|
|
indexInfo->ii_Predicate = make_ands_implicit(predicate);
|
2002-12-15 17:17:59 +01:00
|
|
|
indexInfo->ii_PredicateState = NIL;
|
2000-07-15 00:18:02 +02:00
|
|
|
indexInfo->ii_Unique = unique;
|
2006-08-25 06:06:58 +02:00
|
|
|
indexInfo->ii_Concurrent = concurrent;
|
2000-07-15 00:18:02 +02:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
|
|
|
|
ComputeIndexAttrs(indexInfo, classObjectId, attributeList,
|
2004-05-05 06:48:48 +02:00
|
|
|
relationId, accessMethodName, accessMethodId,
|
|
|
|
isconstraint);
|
|
|
|
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Report index creation if appropriate (delay this till after most of the
|
|
|
|
* error checks)
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
|
|
|
if (isconstraint && !quiet)
|
|
|
|
ereport(NOTICE,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
|
|
|
|
is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
|
|
|
|
primary ? "PRIMARY KEY" : "UNIQUE",
|
|
|
|
indexRelationName, RelationGetRelationName(rel))));
|
2000-02-25 03:58:48 +01:00
|
|
|
|
2006-08-25 06:06:58 +02:00
|
|
|
indexRelationId =
|
|
|
|
index_create(relationId, indexRelationName, indexRelationId,
|
|
|
|
indexInfo, accessMethodId, tablespaceId, classObjectId,
|
|
|
|
reloptions, primary, isconstraint,
|
|
|
|
allowSystemTableMods, skip_build, concurrent);
|
|
|
|
|
|
|
|
if (!concurrent)
|
|
|
|
return; /* We're done, in the standard case */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Phase 2 of concurrent index build (see comments for validate_index()
|
|
|
|
* for an overview of how this works)
|
|
|
|
*
|
|
|
|
* We must commit our current transaction so that the index becomes
|
2006-10-04 02:30:14 +02:00
|
|
|
* visible; then start another. Note that all the data structures we just
|
|
|
|
* built are lost in the commit. The only data we keep past here are the
|
|
|
|
* relation IDs.
|
2006-08-25 06:06:58 +02:00
|
|
|
*
|
|
|
|
* Before committing, get a session-level lock on the table, to ensure
|
2006-10-04 02:30:14 +02:00
|
|
|
* that neither it nor the index can be dropped before we finish. This
|
|
|
|
* cannot block, even if someone else is waiting for access, because we
|
|
|
|
* already have the same lock within our transaction.
|
2006-08-25 06:06:58 +02:00
|
|
|
*
|
|
|
|
* Note: we don't currently bother with a session lock on the index,
|
2006-10-04 02:30:14 +02:00
|
|
|
* because there are no operations that could change its state while we
|
|
|
|
* hold lock on the parent table. This might need to change later.
|
2006-08-25 06:06:58 +02:00
|
|
|
*/
|
|
|
|
heaprelid = rel->rd_lockInfo.lockRelId;
|
|
|
|
LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
|
|
|
|
|
|
|
CommitTransactionCommand();
|
|
|
|
StartTransactionCommand();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we must wait until no running transaction could have the table open
|
2006-10-04 02:30:14 +02:00
|
|
|
* with the old list of indexes. To do this, inquire which xacts
|
|
|
|
* currently would conflict with ShareLock on the table -- ie, which ones
|
|
|
|
* have a lock that permits writing the table. Then wait for each of
|
|
|
|
* these xacts to commit or abort. Note we do not need to worry about
|
|
|
|
* xacts that open the table for writing after this point; they will see
|
|
|
|
* the new index when they open it.
|
2006-08-27 21:14:34 +02:00
|
|
|
*
|
2006-10-04 02:30:14 +02:00
|
|
|
* Note: GetLockConflicts() never reports our own xid, hence we need not
|
|
|
|
* check for that.
|
2006-08-25 06:06:58 +02:00
|
|
|
*/
|
2006-08-27 21:14:34 +02:00
|
|
|
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
|
|
|
|
old_xact_list = GetLockConflicts(&heaplocktag, ShareLock);
|
2006-08-25 06:06:58 +02:00
|
|
|
|
2006-08-27 21:14:34 +02:00
|
|
|
foreach(lc, old_xact_list)
|
|
|
|
{
|
|
|
|
TransactionId xid = lfirst_xid(lc);
|
2006-08-25 06:06:58 +02:00
|
|
|
|
2006-08-27 21:14:34 +02:00
|
|
|
XactLockTableWait(xid);
|
2006-08-25 06:06:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now take the "reference snapshot" that will be used by validate_index()
|
2006-10-04 02:30:14 +02:00
|
|
|
* to filter candidate tuples. All other transactions running at this
|
2006-08-25 06:06:58 +02:00
|
|
|
* time will have to be out-waited before we can commit, because we can't
|
|
|
|
* guarantee that tuples deleted just before this will be in the index.
|
|
|
|
*
|
2006-10-04 02:30:14 +02:00
|
|
|
* We also set ActiveSnapshot to this snap, since functions in indexes may
|
|
|
|
* need a snapshot.
|
2006-08-25 06:06:58 +02:00
|
|
|
*/
|
|
|
|
snapshot = CopySnapshot(GetTransactionSnapshot());
|
|
|
|
ActiveSnapshot = snapshot;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scan the index and the heap, insert any missing index entries.
|
|
|
|
*/
|
|
|
|
validate_index(relationId, indexRelationId, snapshot);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The index is now valid in the sense that it contains all currently
|
2006-10-04 02:30:14 +02:00
|
|
|
* interesting tuples. But since it might not contain tuples deleted just
|
|
|
|
* before the reference snap was taken, we have to wait out any
|
|
|
|
* transactions older than the reference snap. We can do this by waiting
|
|
|
|
* for each xact explicitly listed in the snap.
|
2006-08-25 06:06:58 +02:00
|
|
|
*
|
2006-10-04 02:30:14 +02:00
|
|
|
* Note: GetSnapshotData() never stores our own xid into a snap, hence we
|
|
|
|
* need not check for that.
|
2006-08-25 06:06:58 +02:00
|
|
|
*/
|
|
|
|
for (ixcnt = 0; ixcnt < snapshot->xcnt; ixcnt++)
|
|
|
|
XactLockTableWait(snapshot->xip[ixcnt]);
|
|
|
|
|
|
|
|
/* Index can now be marked valid -- update its pg_index entry */
|
|
|
|
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
|
|
|
|
|
|
|
indexTuple = SearchSysCacheCopy(INDEXRELID,
|
|
|
|
ObjectIdGetDatum(indexRelationId),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(indexTuple))
|
|
|
|
elog(ERROR, "cache lookup failed for index %u", indexRelationId);
|
|
|
|
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
|
|
|
|
|
|
Assert(indexForm->indexrelid = indexRelationId);
|
|
|
|
Assert(!indexForm->indisvalid);
|
|
|
|
|
|
|
|
indexForm->indisvalid = true;
|
|
|
|
|
|
|
|
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
|
|
|
CatalogUpdateIndexes(pg_index, indexTuple);
|
|
|
|
|
|
|
|
heap_close(pg_index, RowExclusiveLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Last thing to do is release the session-level lock on the parent table.
|
|
|
|
*/
|
|
|
|
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CheckPredicate
|
2003-12-28 22:57:37 +01:00
|
|
|
* Checks that the given partial-index predicate is valid.
|
2001-07-16 07:07:00 +02:00
|
|
|
*
|
|
|
|
* This used to also constrain the form of the predicate to forms that
|
2001-10-25 07:50:21 +02:00
|
|
|
* indxpath.c could do something with. However, that seems overly
|
2001-07-16 07:07:00 +02:00
|
|
|
* restrictive. One useful application of partial indexes is to apply
|
|
|
|
* a UNIQUE constraint across a subset of a table, and in that scenario
|
|
|
|
* any evaluatable predicate will work. So accept any predicate here
|
|
|
|
* (except ones requiring a plan), and let indxpath.c fend for itself.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
static void
|
2003-12-28 22:57:37 +01:00
|
|
|
CheckPredicate(Expr *predicate)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2001-07-17 23:53:01 +02:00
|
|
|
/*
|
|
|
|
* We don't currently support generation of an actual query plan for a
|
2005-10-15 04:49:52 +02:00
|
|
|
* predicate, only simple scalar expressions; hence these restrictions.
|
2001-07-17 23:53:01 +02:00
|
|
|
*/
|
2003-12-28 22:57:37 +01:00
|
|
|
if (contain_subplans((Node *) predicate))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("cannot use subquery in index predicate")));
|
2003-12-28 22:57:37 +01:00
|
|
|
if (contain_agg_clause((Node *) predicate))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
|
|
|
errmsg("cannot use aggregate in index predicate")));
|
2001-07-17 23:53:01 +02:00
|
|
|
|
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* A predicate using mutable functions is probably wrong, for the same
|
2003-05-28 18:04:02 +02:00
|
|
|
* reasons that we don't allow an index expression to use one.
|
2001-07-17 23:53:01 +02:00
|
|
|
*/
|
2003-12-28 22:57:37 +01:00
|
|
|
if (contain_mutable_functions((Node *) predicate))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("functions in index predicate must be marked IMMUTABLE")));
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
static void
|
2003-05-28 18:04:02 +02:00
|
|
|
ComputeIndexAttrs(IndexInfo *indexInfo,
|
|
|
|
Oid *classOidP,
|
|
|
|
List *attList, /* list of IndexElem's */
|
|
|
|
Oid relId,
|
|
|
|
char *accessMethodName,
|
2004-05-05 06:48:48 +02:00
|
|
|
Oid accessMethodId,
|
|
|
|
bool isconstraint)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *rest;
|
2000-07-15 00:18:02 +02:00
|
|
|
int attn = 0;
|
1996-08-15 09:42:52 +02:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* process attributeList
|
|
|
|
*/
|
2000-02-25 03:58:48 +01:00
|
|
|
foreach(rest, attList)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2000-07-15 00:18:02 +02:00
|
|
|
IndexElem *attribute = (IndexElem *) lfirst(rest);
|
2003-05-28 18:04:02 +02:00
|
|
|
Oid atttype;
|
2000-02-25 03:58:48 +01:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
if (attribute->name != NULL)
|
|
|
|
{
|
|
|
|
/* Simple index attribute */
|
|
|
|
HeapTuple atttuple;
|
|
|
|
Form_pg_attribute attform;
|
|
|
|
|
|
|
|
Assert(attribute->expr == NULL);
|
|
|
|
atttuple = SearchSysCacheAttName(relId, attribute->name);
|
|
|
|
if (!HeapTupleIsValid(atttuple))
|
2004-05-05 06:48:48 +02:00
|
|
|
{
|
|
|
|
/* difference in error message spellings is historical */
|
|
|
|
if (isconstraint)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("column \"%s\" named in key does not exist",
|
|
|
|
attribute->name)));
|
2004-05-05 06:48:48 +02:00
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
|
|
errmsg("column \"%s\" does not exist",
|
|
|
|
attribute->name)));
|
|
|
|
}
|
2003-05-28 18:04:02 +02:00
|
|
|
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
|
|
|
|
indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum;
|
|
|
|
atttype = attform->atttypid;
|
|
|
|
ReleaseSysCache(atttuple);
|
|
|
|
}
|
|
|
|
else if (attribute->expr && IsA(attribute->expr, Var))
|
|
|
|
{
|
|
|
|
/* Tricky tricky, he wrote (column) ... treat as simple attr */
|
2003-08-04 02:43:34 +02:00
|
|
|
Var *var = (Var *) attribute->expr;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
indexInfo->ii_KeyAttrNumbers[attn] = var->varattno;
|
|
|
|
atttype = get_atttype(relId, var->varattno);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Index expression */
|
|
|
|
Assert(attribute->expr != NULL);
|
2003-08-04 02:43:34 +02:00
|
|
|
indexInfo->ii_KeyAttrNumbers[attn] = 0; /* marks expression */
|
2003-05-28 18:04:02 +02:00
|
|
|
indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions,
|
|
|
|
attribute->expr);
|
|
|
|
atttype = exprType(attribute->expr);
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We don't currently support generation of an actual query plan
|
|
|
|
* for an index expression, only simple scalar expressions; hence
|
|
|
|
* these restrictions.
|
2003-05-28 18:04:02 +02:00
|
|
|
*/
|
|
|
|
if (contain_subplans(attribute->expr))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("cannot use subquery in index expression")));
|
2003-05-28 18:04:02 +02:00
|
|
|
if (contain_agg_clause(attribute->expr))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_GROUPING_ERROR),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("cannot use aggregate function in index expression")));
|
2003-05-28 18:04:02 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* A expression using mutable functions is probably wrong, since
|
|
|
|
* if you aren't going to get the same result for the same data
|
|
|
|
* every time, it's not clear what the index entries mean at all.
|
2003-05-28 18:04:02 +02:00
|
|
|
*/
|
|
|
|
if (contain_mutable_functions(attribute->expr))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
|
|
errmsg("functions in index expression must be marked IMMUTABLE")));
|
2003-05-28 18:04:02 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
classOidP[attn] = GetIndexOpClass(attribute->opclass,
|
|
|
|
atttype,
|
|
|
|
accessMethodName,
|
|
|
|
accessMethodId);
|
2000-07-15 00:18:02 +02:00
|
|
|
attn++;
|
2000-02-25 03:58:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
/*
|
|
|
|
* Resolve possibly-defaulted operator class specification
|
|
|
|
*/
|
2000-02-25 03:58:48 +01:00
|
|
|
static Oid
|
2003-05-28 18:04:02 +02:00
|
|
|
GetIndexOpClass(List *opclass, Oid attrType,
|
|
|
|
char *accessMethodName, Oid accessMethodId)
|
2000-02-25 03:58:48 +01:00
|
|
|
{
|
2002-07-30 01:46:35 +02:00
|
|
|
char *schemaname;
|
|
|
|
char *opcname;
|
2000-02-25 03:58:48 +01:00
|
|
|
HeapTuple tuple;
|
2000-04-25 04:45:54 +02:00
|
|
|
Oid opClassId,
|
2001-08-21 18:36:06 +02:00
|
|
|
opInputType;
|
2000-02-25 03:58:48 +01:00
|
|
|
|
2003-05-28 18:04:02 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Release 7.0 removed network_ops, timespan_ops, and datetime_ops, so we
|
|
|
|
* ignore those opclass names so the default *_ops is used. This can be
|
|
|
|
* removed in some later release. bjm 2000/02/07
|
2003-05-28 18:04:02 +02:00
|
|
|
*
|
2003-08-04 02:43:34 +02:00
|
|
|
* Release 7.1 removes lztext_ops, so suppress that too for a while. tgl
|
|
|
|
* 2000/07/30
|
2003-05-28 18:04:02 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* Release 7.2 renames timestamp_ops to timestamptz_ops, so suppress that
|
|
|
|
* too for awhile. I'm starting to think we need a better approach. tgl
|
2005-10-15 04:49:52 +02:00
|
|
|
* 2000/10/01
|
2003-11-12 22:15:59 +01:00
|
|
|
*
|
2004-08-04 23:34:35 +02:00
|
|
|
* Release 8.0 removes bigbox_ops (which was dead code for a long while
|
2003-11-12 22:15:59 +01:00
|
|
|
* anyway). tgl 2003/11/11
|
2003-05-28 18:04:02 +02:00
|
|
|
*/
|
2004-05-26 06:41:50 +02:00
|
|
|
if (list_length(opclass) == 1)
|
2003-05-28 18:04:02 +02:00
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
char *claname = strVal(linitial(opclass));
|
2003-05-28 18:04:02 +02:00
|
|
|
|
|
|
|
if (strcmp(claname, "network_ops") == 0 ||
|
|
|
|
strcmp(claname, "timespan_ops") == 0 ||
|
|
|
|
strcmp(claname, "datetime_ops") == 0 ||
|
|
|
|
strcmp(claname, "lztext_ops") == 0 ||
|
2003-11-12 22:15:59 +01:00
|
|
|
strcmp(claname, "timestamp_ops") == 0 ||
|
|
|
|
strcmp(claname, "bigbox_ops") == 0)
|
2003-05-28 18:04:02 +02:00
|
|
|
opclass = NIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opclass == NIL)
|
2000-02-25 03:58:48 +01:00
|
|
|
{
|
|
|
|
/* no operator class specified, so find the default */
|
2001-08-21 18:36:06 +02:00
|
|
|
opClassId = GetDefaultOpClass(attrType, accessMethodId);
|
|
|
|
if (!OidIsValid(opClassId))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("data type %s has no default operator class for access method \"%s\"",
|
|
|
|
format_type_be(attrType), accessMethodName),
|
|
|
|
errhint("You must specify an operator class for the index or define a default operator class for the data type.")));
|
2001-08-21 18:36:06 +02:00
|
|
|
return opClassId;
|
2000-02-25 03:58:48 +01:00
|
|
|
}
|
|
|
|
|
2000-04-23 03:44:55 +02:00
|
|
|
/*
|
2002-04-17 22:57:57 +02:00
|
|
|
* Specific opclass name given, so look up the opclass.
|
2000-04-23 03:44:55 +02:00
|
|
|
*/
|
2002-04-17 22:57:57 +02:00
|
|
|
|
|
|
|
/* deconstruct the name list */
|
2003-05-28 18:04:02 +02:00
|
|
|
DeconstructQualifiedName(opclass, &schemaname, &opcname);
|
2002-04-17 22:57:57 +02:00
|
|
|
|
|
|
|
if (schemaname)
|
|
|
|
{
|
|
|
|
/* Look in specific schema only */
|
2002-09-04 22:31:48 +02:00
|
|
|
Oid namespaceId;
|
2002-04-17 22:57:57 +02:00
|
|
|
|
2002-07-30 01:46:35 +02:00
|
|
|
namespaceId = LookupExplicitNamespace(schemaname);
|
2002-04-17 22:57:57 +02:00
|
|
|
tuple = SearchSysCache(CLAAMNAMENSP,
|
|
|
|
ObjectIdGetDatum(accessMethodId),
|
|
|
|
PointerGetDatum(opcname),
|
|
|
|
ObjectIdGetDatum(namespaceId),
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Unqualified opclass name, so search the search path */
|
|
|
|
opClassId = OpclassnameGetOpcid(accessMethodId, opcname);
|
|
|
|
if (!OidIsValid(opClassId))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
|
|
opcname, accessMethodName)));
|
2002-04-17 22:57:57 +02:00
|
|
|
tuple = SearchSysCache(CLAOID,
|
|
|
|
ObjectIdGetDatum(opClassId),
|
|
|
|
0, 0, 0);
|
|
|
|
}
|
|
|
|
|
2001-08-21 18:36:06 +02:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
|
|
errmsg("operator class \"%s\" does not exist for access method \"%s\"",
|
|
|
|
NameListToString(opclass), accessMethodName)));
|
2002-04-17 22:57:57 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Verify that the index operator class accepts this datatype. Note we
|
|
|
|
* will accept binary compatibility.
|
2002-04-17 22:57:57 +02:00
|
|
|
*/
|
2002-07-20 07:16:59 +02:00
|
|
|
opClassId = HeapTupleGetOid(tuple);
|
2001-08-21 18:36:06 +02:00
|
|
|
opInputType = ((Form_pg_opclass) GETSTRUCT(tuple))->opcintype;
|
2000-04-23 03:44:55 +02:00
|
|
|
|
Extend pg_cast castimplicit column to a three-way value; this allows us
to be flexible about assignment casts without introducing ambiguity in
operator/function resolution. Introduce a well-defined promotion hierarchy
for numeric datatypes (int2->int4->int8->numeric->float4->float8).
Change make_const to initially label numeric literals as int4, int8, or
numeric (never float8 anymore).
Explicitly mark Func and RelabelType nodes to indicate whether they came
from a function call, explicit cast, or implicit cast; use this to do
reverse-listing more accurately and without so many heuristics.
Explicit casts to char, varchar, bit, varbit will truncate or pad without
raising an error (the pre-7.2 behavior), while assigning to a column without
any explicit cast will still raise an error for wrong-length data like 7.3.
This more nearly follows the SQL spec than 7.2 behavior (we should be
reporting a 'completion condition' in the explicit-cast cases, but we have
no mechanism for that, so just do silent truncation).
Fix some problems with enforcement of typmod for array elements;
it didn't work at all in 'UPDATE ... SET array[n] = foo', for example.
Provide a generalized array_length_coerce() function to replace the
specialized per-array-type functions that used to be needed (and were
missing for NUMERIC as well as all the datetime types).
Add missing conversions int8<->float4, text<->numeric, oid<->int8.
initdb forced.
2002-09-18 23:35:25 +02:00
|
|
|
if (!IsBinaryCoercible(attrType, opInputType))
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("operator class \"%s\" does not accept data type %s",
|
|
|
|
NameListToString(opclass), format_type_be(attrType))));
|
2002-04-17 22:57:57 +02:00
|
|
|
|
|
|
|
ReleaseSysCache(tuple);
|
2000-04-25 04:45:54 +02:00
|
|
|
|
2001-08-21 18:36:06 +02:00
|
|
|
return opClassId;
|
|
|
|
}
|
|
|
|
|
2006-02-10 20:01:12 +01:00
|
|
|
/*
|
|
|
|
* GetDefaultOpClass
|
|
|
|
*
|
|
|
|
* Given the OIDs of a datatype and an access method, find the default
|
|
|
|
* operator class, if any. Returns InvalidOid if there is none.
|
|
|
|
*/
|
|
|
|
Oid
|
|
|
|
GetDefaultOpClass(Oid type_id, Oid am_id)
|
2001-08-21 18:36:06 +02:00
|
|
|
{
|
2006-12-23 01:43:13 +01:00
|
|
|
Oid result = InvalidOid;
|
2001-08-21 18:36:06 +02:00
|
|
|
int nexact = 0;
|
|
|
|
int ncompatible = 0;
|
2006-12-23 01:43:13 +01:00
|
|
|
int ncompatiblepreferred = 0;
|
2006-02-10 20:01:12 +01:00
|
|
|
Relation rel;
|
|
|
|
ScanKeyData skey[1];
|
|
|
|
SysScanDesc scan;
|
|
|
|
HeapTuple tup;
|
2006-12-23 01:43:13 +01:00
|
|
|
CATEGORY tcategory;
|
2000-02-25 03:58:48 +01:00
|
|
|
|
2002-08-16 22:55:09 +02:00
|
|
|
/* If it's a domain, look at the base type instead */
|
2006-02-10 20:01:12 +01:00
|
|
|
type_id = getBaseType(type_id);
|
2002-08-16 22:55:09 +02:00
|
|
|
|
2006-12-23 01:43:13 +01:00
|
|
|
tcategory = TypeCategory(type_id);
|
|
|
|
|
2000-04-25 04:45:54 +02:00
|
|
|
/*
|
2001-08-21 18:36:06 +02:00
|
|
|
* We scan through all the opclasses available for the access method,
|
|
|
|
* looking for one that is marked default and matches the target type
|
|
|
|
* (either exactly or binary-compatibly, but prefer an exact match).
|
|
|
|
*
|
2006-12-23 01:43:13 +01:00
|
|
|
* We could find more than one binary-compatible match. If just one is
|
|
|
|
* for a preferred type, use that one; otherwise we fail, forcing the user
|
|
|
|
* to specify which one he wants. (The preferred-type special case is a
|
|
|
|
* kluge for varchar: it's binary-compatible to both text and bpchar, so
|
|
|
|
* we need a tiebreaker.) If we find more than one exact match, then
|
|
|
|
* someone put bogus entries in pg_opclass.
|
2000-04-25 04:45:54 +02:00
|
|
|
*/
|
2006-02-10 20:01:12 +01:00
|
|
|
rel = heap_open(OperatorClassRelationId, AccessShareLock);
|
|
|
|
|
|
|
|
ScanKeyInit(&skey[0],
|
2006-12-23 01:43:13 +01:00
|
|
|
Anum_pg_opclass_opcmethod,
|
2006-02-10 20:01:12 +01:00
|
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(am_id));
|
|
|
|
|
|
|
|
scan = systable_beginscan(rel, OpclassAmNameNspIndexId, true,
|
|
|
|
SnapshotNow, 1, skey);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
2000-04-25 04:45:54 +02:00
|
|
|
{
|
2006-02-10 20:01:12 +01:00
|
|
|
Form_pg_opclass opclass = (Form_pg_opclass) GETSTRUCT(tup);
|
|
|
|
|
2006-12-23 01:43:13 +01:00
|
|
|
/* ignore altogether if not a default opclass */
|
|
|
|
if (!opclass->opcdefault)
|
|
|
|
continue;
|
|
|
|
if (opclass->opcintype == type_id)
|
|
|
|
{
|
|
|
|
nexact++;
|
|
|
|
result = HeapTupleGetOid(tup);
|
|
|
|
}
|
|
|
|
else if (nexact == 0 &&
|
|
|
|
IsBinaryCoercible(type_id, opclass->opcintype))
|
2000-04-25 04:45:54 +02:00
|
|
|
{
|
2006-12-23 01:43:13 +01:00
|
|
|
if (IsPreferredType(tcategory, opclass->opcintype))
|
2001-08-21 18:36:06 +02:00
|
|
|
{
|
2006-12-23 01:43:13 +01:00
|
|
|
ncompatiblepreferred++;
|
|
|
|
result = HeapTupleGetOid(tup);
|
2001-08-21 18:36:06 +02:00
|
|
|
}
|
2006-12-23 01:43:13 +01:00
|
|
|
else if (ncompatiblepreferred == 0)
|
2001-08-21 18:36:06 +02:00
|
|
|
{
|
|
|
|
ncompatible++;
|
2006-12-23 01:43:13 +01:00
|
|
|
result = HeapTupleGetOid(tup);
|
2001-08-21 18:36:06 +02:00
|
|
|
}
|
2000-04-25 04:45:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-02-10 20:01:12 +01:00
|
|
|
systable_endscan(scan);
|
|
|
|
|
|
|
|
heap_close(rel, AccessShareLock);
|
|
|
|
|
2006-12-23 01:43:13 +01:00
|
|
|
/* raise error if pg_opclass contains inconsistent data */
|
|
|
|
if (nexact > 1)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("there are multiple default operator classes for data type %s",
|
2006-02-10 20:01:12 +01:00
|
|
|
format_type_be(type_id))));
|
2006-12-23 01:43:13 +01:00
|
|
|
|
|
|
|
if (nexact == 1 ||
|
|
|
|
ncompatiblepreferred == 1 ||
|
|
|
|
(ncompatiblepreferred == 0 && ncompatible == 1))
|
|
|
|
return result;
|
2000-11-16 23:30:52 +01:00
|
|
|
|
2001-08-21 18:36:06 +02:00
|
|
|
return InvalidOid;
|
1996-08-15 09:42:52 +02:00
|
|
|
}
|
|
|
|
|
2004-05-05 06:48:48 +02:00
|
|
|
/*
|
2004-06-10 19:56:03 +02:00
|
|
|
* makeObjectName()
|
|
|
|
*
|
|
|
|
* Create a name for an implicitly created index, sequence, constraint, etc.
|
|
|
|
*
|
|
|
|
* The parameters are typically: the original table name, the original field
|
|
|
|
* name, and a "type" string (such as "seq" or "pkey"). The field name
|
|
|
|
* and/or type can be NULL if not relevant.
|
|
|
|
*
|
|
|
|
* The result is a palloc'd string.
|
|
|
|
*
|
|
|
|
* The basic result we want is "name1_name2_label", omitting "_name2" or
|
|
|
|
* "_label" when those parameters are NULL. However, we must generate
|
|
|
|
* a name with less than NAMEDATALEN characters! So, we truncate one or
|
|
|
|
* both names if necessary to make a short-enough string. The label part
|
|
|
|
* is never truncated (so it had better be reasonably short).
|
|
|
|
*
|
|
|
|
* The caller is responsible for checking uniqueness of the generated
|
|
|
|
* name and retrying as needed; retrying will be done by altering the
|
|
|
|
* "label" string (which is why we never truncate that part).
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
2004-06-10 19:56:03 +02:00
|
|
|
char *
|
|
|
|
makeObjectName(const char *name1, const char *name2, const char *label)
|
2004-05-05 06:48:48 +02:00
|
|
|
{
|
2004-06-10 19:56:03 +02:00
|
|
|
char *name;
|
|
|
|
int overhead = 0; /* chars needed for label and underscores */
|
|
|
|
int availchars; /* chars available for name(s) */
|
|
|
|
int name1chars; /* chars allocated to name1 */
|
|
|
|
int name2chars; /* chars allocated to name2 */
|
|
|
|
int ndx;
|
|
|
|
|
|
|
|
name1chars = strlen(name1);
|
|
|
|
if (name2)
|
|
|
|
{
|
|
|
|
name2chars = strlen(name2);
|
|
|
|
overhead++; /* allow for separating underscore */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
name2chars = 0;
|
|
|
|
if (label)
|
|
|
|
overhead += strlen(label) + 1;
|
|
|
|
|
|
|
|
availchars = NAMEDATALEN - 1 - overhead;
|
|
|
|
Assert(availchars > 0); /* else caller chose a bad label */
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
/*
|
2004-06-10 19:56:03 +02:00
|
|
|
* If we must truncate, preferentially truncate the longer name. This
|
2005-10-15 04:49:52 +02:00
|
|
|
* logic could be expressed without a loop, but it's simple and obvious as
|
|
|
|
* a loop.
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
2004-06-10 19:56:03 +02:00
|
|
|
while (name1chars + name2chars > availchars)
|
|
|
|
{
|
|
|
|
if (name1chars > name2chars)
|
|
|
|
name1chars--;
|
|
|
|
else
|
|
|
|
name2chars--;
|
|
|
|
}
|
|
|
|
|
2005-06-21 02:35:05 +02:00
|
|
|
name1chars = pg_mbcliplen(name1, name1chars, name1chars);
|
2004-06-10 19:56:03 +02:00
|
|
|
if (name2)
|
|
|
|
name2chars = pg_mbcliplen(name2, name2chars, name2chars);
|
|
|
|
|
|
|
|
/* Now construct the string using the chosen lengths */
|
|
|
|
name = palloc(name1chars + name2chars + overhead + 1);
|
|
|
|
memcpy(name, name1, name1chars);
|
|
|
|
ndx = name1chars;
|
|
|
|
if (name2)
|
|
|
|
{
|
|
|
|
name[ndx++] = '_';
|
|
|
|
memcpy(name + ndx, name2, name2chars);
|
|
|
|
ndx += name2chars;
|
|
|
|
}
|
|
|
|
if (label)
|
|
|
|
{
|
|
|
|
name[ndx++] = '_';
|
|
|
|
strcpy(name + ndx, label);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
name[ndx] = '\0';
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Select a nonconflicting name for a new relation. This is ordinarily
|
|
|
|
* used to choose index names (which is why it's here) but it can also
|
|
|
|
* be used for sequences, or any autogenerated relation kind.
|
|
|
|
*
|
|
|
|
* name1, name2, and label are used the same way as for makeObjectName(),
|
|
|
|
* except that the label can't be NULL; digits will be appended to the label
|
|
|
|
* if needed to create a name that is unique within the specified namespace.
|
|
|
|
*
|
|
|
|
* Note: it is theoretically possible to get a collision anyway, if someone
|
|
|
|
* else chooses the same name concurrently. This is fairly unlikely to be
|
|
|
|
* a problem in practice, especially if one is holding an exclusive lock on
|
|
|
|
* the relation identified by name1. However, if choosing multiple names
|
|
|
|
* within a single command, you'd better create the new object and do
|
|
|
|
* CommandCounterIncrement before choosing the next one!
|
|
|
|
*
|
|
|
|
* Returns a palloc'd string.
|
|
|
|
*/
|
|
|
|
char *
|
|
|
|
ChooseRelationName(const char *name1, const char *name2,
|
|
|
|
const char *label, Oid namespace)
|
|
|
|
{
|
|
|
|
int pass = 0;
|
|
|
|
char *relname = NULL;
|
|
|
|
char modlabel[NAMEDATALEN];
|
|
|
|
|
|
|
|
/* try the unmodified label first */
|
|
|
|
StrNCpy(modlabel, label, sizeof(modlabel));
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2004-06-10 19:56:03 +02:00
|
|
|
relname = makeObjectName(name1, name2, modlabel);
|
2004-05-05 06:48:48 +02:00
|
|
|
|
2004-06-10 19:56:03 +02:00
|
|
|
if (!OidIsValid(get_relname_relid(relname, namespace)))
|
2004-05-05 06:48:48 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
/* found a conflict, so try a new name component */
|
2004-06-10 19:56:03 +02:00
|
|
|
pfree(relname);
|
|
|
|
snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
|
2004-05-05 06:48:48 +02:00
|
|
|
}
|
|
|
|
|
2004-06-10 19:56:03 +02:00
|
|
|
return relname;
|
2004-05-05 06:48:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* relationHasPrimaryKey -
|
|
|
|
*
|
|
|
|
* See whether an existing relation has a primary key.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
relationHasPrimaryKey(Relation rel)
|
|
|
|
{
|
|
|
|
bool result = false;
|
2004-05-26 06:41:50 +02:00
|
|
|
List *indexoidlist;
|
|
|
|
ListCell *indexoidscan;
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the list of index OIDs for the table from the relcache, and look up
|
|
|
|
* each one in the pg_index syscache until we find one marked primary key
|
|
|
|
* (hopefully there isn't more than one such).
|
2004-05-05 06:48:48 +02:00
|
|
|
*/
|
|
|
|
indexoidlist = RelationGetIndexList(rel);
|
|
|
|
|
|
|
|
foreach(indexoidscan, indexoidlist)
|
|
|
|
{
|
2004-05-26 06:41:50 +02:00
|
|
|
Oid indexoid = lfirst_oid(indexoidscan);
|
2004-05-05 06:48:48 +02:00
|
|
|
HeapTuple indexTuple;
|
|
|
|
|
|
|
|
indexTuple = SearchSysCache(INDEXRELID,
|
|
|
|
ObjectIdGetDatum(indexoid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(indexTuple)) /* should not happen */
|
|
|
|
elog(ERROR, "cache lookup failed for index %u", indexoid);
|
|
|
|
result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
|
|
|
|
ReleaseSysCache(indexTuple);
|
|
|
|
if (result)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2004-05-26 06:41:50 +02:00
|
|
|
list_free(indexoidlist);
|
2004-05-05 06:48:48 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1999-05-25 18:15:34 +02:00
|
|
|
* RemoveIndex
|
1997-09-07 07:04:48 +02:00
|
|
|
* Deletes an index.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
2002-07-01 17:27:56 +02:00
|
|
|
RemoveIndex(RangeVar *relation, DropBehavior behavior)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2002-03-26 20:17:02 +01:00
|
|
|
Oid indOid;
|
2002-09-20 01:40:56 +02:00
|
|
|
char relkind;
|
2002-07-12 20:43:19 +02:00
|
|
|
ObjectAddress object;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-03-26 20:17:02 +01:00
|
|
|
indOid = RangeVarGetRelid(relation, false);
|
2002-09-20 01:40:56 +02:00
|
|
|
relkind = get_rel_relkind(indOid);
|
|
|
|
if (relkind != RELKIND_INDEX)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("\"%s\" is not an index",
|
2003-07-20 23:56:35 +02:00
|
|
|
relation->relname)));
|
2002-03-26 20:17:02 +01:00
|
|
|
|
2005-04-14 03:38:22 +02:00
|
|
|
object.classId = RelationRelationId;
|
2002-07-12 20:43:19 +02:00
|
|
|
object.objectId = indOid;
|
|
|
|
object.objectSubId = 0;
|
|
|
|
|
|
|
|
performDeletion(&object, behavior);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2000-02-18 10:30:20 +01:00
|
|
|
|
|
|
|
/*
|
2002-08-29 17:56:20 +02:00
|
|
|
* ReindexIndex
|
2005-06-22 23:14:31 +02:00
|
|
|
* Recreate a specific index.
|
2000-02-18 10:30:20 +01:00
|
|
|
*/
|
|
|
|
void
|
2005-06-22 23:14:31 +02:00
|
|
|
ReindexIndex(RangeVar *indexRelation)
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2002-03-26 20:17:02 +01:00
|
|
|
Oid indOid;
|
2000-02-18 10:30:20 +01:00
|
|
|
HeapTuple tuple;
|
2000-11-08 23:10:03 +01:00
|
|
|
|
2002-03-26 20:17:02 +01:00
|
|
|
indOid = RangeVarGetRelid(indexRelation, false);
|
|
|
|
tuple = SearchSysCache(RELOID,
|
|
|
|
ObjectIdGetDatum(indOid),
|
2000-11-16 23:30:52 +01:00
|
|
|
0, 0, 0);
|
2003-08-04 02:43:34 +02:00
|
|
|
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
|
2003-07-20 23:56:35 +02:00
|
|
|
elog(ERROR, "cache lookup failed for relation %u", indOid);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
|
|
|
if (((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_INDEX)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("\"%s\" is not an index",
|
2003-07-20 23:56:35 +02:00
|
|
|
indexRelation->relname)));
|
2002-03-26 20:17:02 +01:00
|
|
|
|
2003-09-24 20:54:02 +02:00
|
|
|
/* Check permissions */
|
|
|
|
if (!pg_class_ownercheck(indOid, GetUserId()))
|
|
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
|
|
indexRelation->relname);
|
2002-04-12 22:38:31 +02:00
|
|
|
|
2002-03-26 20:17:02 +01:00
|
|
|
ReleaseSysCache(tuple);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2003-09-24 20:54:02 +02:00
|
|
|
reindex_index(indOid);
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ReindexTable
|
2005-06-22 23:14:31 +02:00
|
|
|
* Recreate all indexes of a table (and of its toast table, if any)
|
2000-02-18 10:30:20 +01:00
|
|
|
*/
|
|
|
|
void
|
2005-06-22 23:14:31 +02:00
|
|
|
ReindexTable(RangeVar *relation)
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2002-03-26 20:17:02 +01:00
|
|
|
Oid heapOid;
|
2003-09-24 20:54:02 +02:00
|
|
|
HeapTuple tuple;
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2002-03-26 20:17:02 +01:00
|
|
|
heapOid = RangeVarGetRelid(relation, false);
|
2003-09-24 20:54:02 +02:00
|
|
|
tuple = SearchSysCache(RELOID,
|
|
|
|
ObjectIdGetDatum(heapOid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
|
|
|
|
elog(ERROR, "cache lookup failed for relation %u", heapOid);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2003-09-24 20:54:02 +02:00
|
|
|
if (((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_RELATION &&
|
|
|
|
((Form_pg_class) GETSTRUCT(tuple))->relkind != RELKIND_TOASTVALUE)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2003-09-29 18:37:29 +02:00
|
|
|
errmsg("\"%s\" is not a table",
|
2003-07-20 23:56:35 +02:00
|
|
|
relation->relname)));
|
2000-11-16 23:30:52 +01:00
|
|
|
|
2003-09-24 20:54:02 +02:00
|
|
|
/* Check permissions */
|
|
|
|
if (!pg_class_ownercheck(heapOid, GetUserId()))
|
|
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
|
|
|
|
relation->relname);
|
|
|
|
|
|
|
|
/* Can't reindex shared tables except in standalone mode */
|
|
|
|
if (((Form_pg_class) GETSTRUCT(tuple))->relisshared && IsUnderPostmaster)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
2003-09-29 02:05:25 +02:00
|
|
|
errmsg("shared table \"%s\" can only be reindexed in stand-alone mode",
|
2003-09-24 20:54:02 +02:00
|
|
|
relation->relname)));
|
|
|
|
|
|
|
|
ReleaseSysCache(tuple);
|
2002-10-22 00:06:20 +02:00
|
|
|
|
2004-05-08 02:34:49 +02:00
|
|
|
if (!reindex_relation(heapOid, true))
|
2003-10-02 08:34:04 +02:00
|
|
|
ereport(NOTICE,
|
2003-09-24 20:54:02 +02:00
|
|
|
(errmsg("table \"%s\" has no indexes",
|
2003-07-20 23:56:35 +02:00
|
|
|
relation->relname)));
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ReindexDatabase
|
|
|
|
* Recreate indexes of a database.
|
2003-09-24 20:54:02 +02:00
|
|
|
*
|
|
|
|
* To reduce the probability of deadlocks, each table is reindexed in a
|
|
|
|
* separate transaction, so we can release the lock on it right away.
|
2000-02-18 10:30:20 +01:00
|
|
|
*/
|
|
|
|
void
|
2005-06-22 23:14:31 +02:00
|
|
|
ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
Relation relationRelation;
|
2000-04-12 19:17:23 +02:00
|
|
|
HeapScanDesc scan;
|
2004-08-29 07:07:03 +02:00
|
|
|
HeapTuple tuple;
|
2000-06-28 05:33:33 +02:00
|
|
|
MemoryContext private_context;
|
2000-04-12 19:17:23 +02:00
|
|
|
MemoryContext old;
|
2004-08-29 07:07:03 +02:00
|
|
|
List *relids = NIL;
|
|
|
|
ListCell *l;
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2005-06-22 23:14:31 +02:00
|
|
|
AssertArg(databaseName);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2005-06-22 23:14:31 +02:00
|
|
|
if (strcmp(databaseName, get_database_name(MyDatabaseId)) != 0)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("can only reindex the currently open database")));
|
2000-06-28 05:33:33 +02:00
|
|
|
|
2003-06-27 16:45:32 +02:00
|
|
|
if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
|
2003-08-01 02:15:26 +02:00
|
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
|
2005-06-22 23:14:31 +02:00
|
|
|
databaseName);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* We cannot run inside a user transaction block; if we were inside a
|
2005-10-15 04:49:52 +02:00
|
|
|
* transaction, then our commit- and start-transaction-command calls would
|
|
|
|
* not have the intended effect!
|
2000-06-28 05:33:33 +02:00
|
|
|
*/
|
2005-06-22 23:14:31 +02:00
|
|
|
PreventTransactionChain((void *) databaseName, "REINDEX DATABASE");
|
2002-10-19 22:15:09 +02:00
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Create a memory context that will survive forced transaction commits we
|
|
|
|
* do below. Since it is a child of PortalContext, it will go away
|
|
|
|
* eventually even if we suffer an error; there's no need for special
|
|
|
|
* abort cleanup logic.
|
2000-06-28 05:33:33 +02:00
|
|
|
*/
|
2003-05-02 22:54:36 +02:00
|
|
|
private_context = AllocSetContextCreate(PortalContext,
|
2000-06-28 05:33:33 +02:00
|
|
|
"ReindexDatabase",
|
|
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
2000-02-18 10:30:20 +01:00
|
|
|
|
2003-09-24 20:54:02 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We always want to reindex pg_class first. This ensures that if there
|
|
|
|
* is any corruption in pg_class' indexes, they will be fixed before we
|
|
|
|
* process any other tables. This is critical because reindexing itself
|
|
|
|
* will try to update pg_class.
|
2003-09-24 20:54:02 +02:00
|
|
|
*/
|
2005-06-22 23:14:31 +02:00
|
|
|
if (do_system)
|
|
|
|
{
|
|
|
|
old = MemoryContextSwitchTo(private_context);
|
|
|
|
relids = lappend_oid(relids, RelationRelationId);
|
|
|
|
MemoryContextSwitchTo(old);
|
|
|
|
}
|
2003-09-24 20:54:02 +02:00
|
|
|
|
2001-11-20 03:46:13 +01:00
|
|
|
/*
|
|
|
|
* Scan pg_class to build a list of the relations we need to reindex.
|
2003-09-24 20:54:02 +02:00
|
|
|
*
|
|
|
|
* We only consider plain relations here (toast rels will be processed
|
|
|
|
* indirectly by reindex_relation).
|
2001-11-20 03:46:13 +01:00
|
|
|
*/
|
2005-04-14 22:03:27 +02:00
|
|
|
relationRelation = heap_open(RelationRelationId, AccessShareLock);
|
2002-05-21 01:51:44 +02:00
|
|
|
scan = heap_beginscan(relationRelation, SnapshotNow, 0, NULL);
|
|
|
|
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2003-09-24 20:54:02 +02:00
|
|
|
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
|
|
|
|
|
|
|
|
if (classtuple->relkind != RELKIND_RELATION)
|
|
|
|
continue;
|
2002-08-29 17:56:20 +02:00
|
|
|
|
2005-06-22 23:14:31 +02:00
|
|
|
/* Check user/system classification, and optionally skip */
|
|
|
|
if (IsSystemClass(classtuple))
|
|
|
|
{
|
|
|
|
if (!do_system)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2005-06-22 23:14:31 +02:00
|
|
|
if (!do_user)
|
2000-02-18 10:30:20 +01:00
|
|
|
continue;
|
|
|
|
}
|
2003-09-24 20:54:02 +02:00
|
|
|
|
|
|
|
if (IsUnderPostmaster) /* silently ignore shared tables */
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2003-09-24 20:54:02 +02:00
|
|
|
if (classtuple->relisshared)
|
|
|
|
continue;
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|
2003-09-24 20:54:02 +02:00
|
|
|
|
2005-04-14 03:38:22 +02:00
|
|
|
if (HeapTupleGetOid(tuple) == RelationRelationId)
|
2003-09-24 20:54:02 +02:00
|
|
|
continue; /* got it already */
|
|
|
|
|
|
|
|
old = MemoryContextSwitchTo(private_context);
|
2004-05-26 06:41:50 +02:00
|
|
|
relids = lappend_oid(relids, HeapTupleGetOid(tuple));
|
2003-09-24 20:54:02 +02:00
|
|
|
MemoryContextSwitchTo(old);
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(relationRelation, AccessShareLock);
|
|
|
|
|
2001-11-20 03:46:13 +01:00
|
|
|
/* Now reindex each rel in a separate transaction */
|
2003-05-14 05:26:03 +02:00
|
|
|
CommitTransactionCommand();
|
2004-05-26 06:41:50 +02:00
|
|
|
foreach(l, relids)
|
2000-02-18 10:30:20 +01:00
|
|
|
{
|
2004-08-29 07:07:03 +02:00
|
|
|
Oid relid = lfirst_oid(l);
|
2003-09-24 20:54:02 +02:00
|
|
|
|
2003-05-14 05:26:03 +02:00
|
|
|
StartTransactionCommand();
|
2004-09-13 22:10:13 +02:00
|
|
|
/* functions in indexes may want a snapshot set */
|
|
|
|
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
|
2004-05-08 02:34:49 +02:00
|
|
|
if (reindex_relation(relid, true))
|
2006-06-07 19:20:17 +02:00
|
|
|
ereport(NOTICE,
|
2003-09-24 20:54:02 +02:00
|
|
|
(errmsg("table \"%s\" was reindexed",
|
|
|
|
get_rel_name(relid))));
|
2003-05-14 05:26:03 +02:00
|
|
|
CommitTransactionCommand();
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|
2003-05-14 05:26:03 +02:00
|
|
|
StartTransactionCommand();
|
2000-06-28 05:33:33 +02:00
|
|
|
|
|
|
|
MemoryContextDelete(private_context);
|
2000-02-18 10:30:20 +01:00
|
|
|
}
|