1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* command.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* random postgres portal and utility support code
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2001-01-24 20:43:33 +01:00
|
|
|
* Portions Copyright (c) 1996-2001, 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
|
2002-03-05 06:33:31 +01:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.158 2002/03/05 05:33:08 momjian Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
* NOTES
|
1997-09-07 07:04:48 +02:00
|
|
|
* The PerformAddAttribute() code, like most of the relation
|
|
|
|
* manipulating code in the commands/ directory, should go
|
|
|
|
* someplace closer to the lib/catalog code.
|
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "postgres.h"
|
|
|
|
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "access/genam.h"
|
2000-08-25 20:05:54 +02:00
|
|
|
#include "access/tuptoaster.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "catalog/catalog.h"
|
|
|
|
#include "catalog/catname.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "catalog/heap.h"
|
2000-07-04 08:11:54 +02:00
|
|
|
#include "catalog/index.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "catalog/indexing.h"
|
2000-01-16 21:05:00 +01:00
|
|
|
#include "catalog/pg_attrdef.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "catalog/pg_index.h"
|
2000-07-04 08:11:54 +02:00
|
|
|
#include "catalog/pg_opclass.h"
|
2002-02-19 21:11:20 +01:00
|
|
|
#include "catalog/pg_relcheck.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "catalog/pg_type.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "commands/command.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "commands/trigger.h"
|
2001-10-25 07:50:21 +02:00
|
|
|
#include "commands/defrem.h" /* For add constraint unique, primary */
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "executor/execdefs.h"
|
|
|
|
#include "executor/executor.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "miscadmin.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "optimizer/clauses.h"
|
|
|
|
#include "optimizer/planmain.h"
|
1998-04-27 06:08:07 +02:00
|
|
|
#include "optimizer/prep.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "parser/parse.h"
|
2000-07-15 14:37:14 +02:00
|
|
|
#include "parser/parse_expr.h"
|
2001-05-09 23:10:39 +02:00
|
|
|
#include "parser/parse_oper.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "parser/parse_relation.h"
|
2001-10-25 07:50:21 +02:00
|
|
|
#include "parser/analyze.h" /* For add constraint unique, primary */
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "utils/acl.h"
|
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/fmgroids.h"
|
|
|
|
#include "utils/lsyscache.h"
|
|
|
|
#include "utils/syscache.h"
|
2000-08-29 06:20:47 +02:00
|
|
|
#include "utils/relcache.h"
|
2001-06-14 03:09:22 +02:00
|
|
|
#include "utils/temprel.h"
|
2000-01-16 21:05:00 +01:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
static void drop_default(Oid relid, int16 attnum);
|
2000-08-25 20:05:54 +02:00
|
|
|
static bool needs_toast_table(Relation rel);
|
2000-09-12 06:30:08 +02:00
|
|
|
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/* --------------------------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* PortalCleanup
|
1996-07-09 08:22:35 +02:00
|
|
|
* --------------------------------
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
PortalCleanup(Portal portal)
|
|
|
|
{
|
2000-06-28 05:33:33 +02:00
|
|
|
MemoryContext oldcontext;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* sanity checks
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
AssertArg(PortalIsValid(portal));
|
|
|
|
AssertArg(portal->cleanup == PortalCleanup);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* set proper portal-executor context before calling ExecMain.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2000-06-28 05:33:33 +02:00
|
|
|
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* tell the executor to shutdown the query
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
ExecutorEnd(PortalGetQueryDesc(portal), PortalGetState(portal));
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* switch back to previous context
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2000-06-28 05:33:33 +02:00
|
|
|
MemoryContextSwitchTo(oldcontext);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2002-02-26 23:47:12 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* PerformPortalFetch
|
|
|
|
*
|
|
|
|
* name: name of portal
|
|
|
|
* forward: forward or backward fetch?
|
|
|
|
* count: # of tuples to fetch (0 implies all)
|
|
|
|
* dest: where to send results
|
|
|
|
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
|
|
|
|
* in which to store a command completion status string.
|
|
|
|
*
|
|
|
|
* completionTag may be NULL if caller doesn't want a status string.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
PerformPortalFetch(char *name,
|
1997-09-07 07:04:48 +02:00
|
|
|
bool forward,
|
|
|
|
int count,
|
2002-02-26 23:47:12 +01:00
|
|
|
CommandDest dest,
|
|
|
|
char *completionTag)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1998-02-26 05:46:47 +01:00
|
|
|
Portal portal;
|
|
|
|
QueryDesc *queryDesc;
|
2001-02-27 23:07:34 +01:00
|
|
|
EState *estate;
|
2000-06-28 05:33:33 +02:00
|
|
|
MemoryContext oldcontext;
|
2002-02-27 20:36:13 +01:00
|
|
|
ScanDirection direction;
|
2002-02-14 16:24:10 +01:00
|
|
|
CommandId savedId;
|
2001-08-10 20:57:42 +02:00
|
|
|
bool temp_desc = false;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-02-26 23:47:12 +01:00
|
|
|
/* initialize completion status in case of early exit */
|
|
|
|
if (completionTag)
|
|
|
|
strcpy(completionTag, (dest == None) ? "MOVE 0" : "FETCH 0");
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* sanity checks
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
if (name == NULL)
|
|
|
|
{
|
2000-06-28 05:33:33 +02:00
|
|
|
elog(NOTICE, "PerformPortalFetch: missing portal name");
|
1997-09-07 07:04:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get the portal from the portal name
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
portal = GetPortalByName(name);
|
|
|
|
if (!PortalIsValid(portal))
|
|
|
|
{
|
|
|
|
elog(NOTICE, "PerformPortalFetch: portal \"%s\" not found",
|
|
|
|
name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* switch into the portal context
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2000-06-28 05:33:33 +02:00
|
|
|
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
queryDesc = PortalGetQueryDesc(portal);
|
|
|
|
estate = PortalGetState(portal);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-08-10 20:57:42 +02:00
|
|
|
* If the requested destination is not the same as the query's
|
|
|
|
* original destination, make a temporary QueryDesc with the proper
|
|
|
|
* destination. This supports MOVE, for example, which will pass in
|
|
|
|
* dest = None.
|
2001-02-27 23:07:34 +01:00
|
|
|
*
|
2001-10-25 07:50:21 +02:00
|
|
|
* EXCEPTION: if the query's original dest is RemoteInternal (ie, it's a
|
|
|
|
* binary cursor) and the request is Remote, we do NOT override the
|
2001-08-10 20:57:42 +02:00
|
|
|
* original dest. This is necessary since a FETCH command will pass
|
|
|
|
* dest = Remote, not knowing whether the cursor is binary or not.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2001-08-10 20:57:42 +02:00
|
|
|
if (dest != queryDesc->dest &&
|
|
|
|
!(queryDesc->dest == RemoteInternal && dest == Remote))
|
1997-10-27 09:55:16 +01:00
|
|
|
{
|
1998-02-26 05:46:47 +01:00
|
|
|
QueryDesc *qdesc = (QueryDesc *) palloc(sizeof(QueryDesc));
|
|
|
|
|
|
|
|
memcpy(qdesc, queryDesc, sizeof(QueryDesc));
|
1997-10-27 09:55:16 +01:00
|
|
|
qdesc->dest = dest;
|
|
|
|
queryDesc = qdesc;
|
2001-08-10 20:57:42 +02:00
|
|
|
temp_desc = true;
|
1997-10-27 09:55:16 +01:00
|
|
|
}
|
1998-02-26 05:46:47 +01:00
|
|
|
|
2002-02-14 16:24:10 +01:00
|
|
|
/*
|
|
|
|
* Restore the scanCommandId that was current when the cursor was
|
|
|
|
* opened. This ensures that we see the same tuples throughout the
|
|
|
|
* execution of the cursor.
|
|
|
|
*/
|
|
|
|
savedId = GetScanCommandId();
|
|
|
|
SetScanCommandId(PortalGetCommandId(portal));
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Determine which direction to go in, and check to see if we're
|
|
|
|
* already at the end of the available tuples in that direction. If
|
2002-02-27 20:36:13 +01:00
|
|
|
* so, set the direction to NoMovement to avoid trying to fetch any
|
|
|
|
* tuples. (This check exists because not all plan node types
|
2001-03-22 07:16:21 +01:00
|
|
|
* are robust about being called again if they've already returned
|
2002-02-27 20:36:13 +01:00
|
|
|
* NULL once.) Then call the executor (we must not skip this, because
|
|
|
|
* the destination needs to see a setup and shutdown even if no tuples
|
|
|
|
* are available). Finally, update the atStart/atEnd state depending
|
|
|
|
* on the number of tuples that were retrieved.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2001-02-27 23:07:34 +01:00
|
|
|
if (forward)
|
|
|
|
{
|
2002-02-27 20:36:13 +01:00
|
|
|
if (portal->atEnd)
|
|
|
|
direction = NoMovementScanDirection;
|
|
|
|
else
|
|
|
|
direction = ForwardScanDirection;
|
2001-10-25 07:50:21 +02:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
ExecutorRun(queryDesc, estate, direction, (long) count);
|
2002-02-26 23:47:12 +01:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
if (estate->es_processed > 0)
|
|
|
|
portal->atStart = false; /* OK to back up now */
|
|
|
|
if (count <= 0 || (int) estate->es_processed < count)
|
|
|
|
portal->atEnd = true; /* we retrieved 'em all */
|
2001-02-27 23:07:34 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2002-02-27 20:36:13 +01:00
|
|
|
if (portal->atStart)
|
|
|
|
direction = NoMovementScanDirection;
|
|
|
|
else
|
|
|
|
direction = BackwardScanDirection;
|
2001-10-25 07:50:21 +02:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
ExecutorRun(queryDesc, estate, direction, (long) count);
|
2002-02-26 23:47:12 +01:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
if (estate->es_processed > 0)
|
|
|
|
portal->atEnd = false; /* OK to go forward now */
|
|
|
|
if (count <= 0 || (int) estate->es_processed < count)
|
|
|
|
portal->atStart = true; /* we retrieved 'em all */
|
2001-02-27 23:07:34 +01:00
|
|
|
}
|
1998-02-26 05:46:47 +01:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
/* Return command status if wanted */
|
|
|
|
if (completionTag)
|
|
|
|
snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %u",
|
|
|
|
(dest == None) ? "MOVE" : "FETCH",
|
|
|
|
estate->es_processed);
|
|
|
|
|
2002-02-14 16:24:10 +01:00
|
|
|
/*
|
|
|
|
* Restore outer command ID.
|
|
|
|
*/
|
|
|
|
SetScanCommandId(savedId);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Clean up and switch back to old context.
|
2000-06-28 05:33:33 +02:00
|
|
|
*/
|
2001-08-10 20:57:42 +02:00
|
|
|
if (temp_desc)
|
2001-02-27 23:07:34 +01:00
|
|
|
pfree(queryDesc);
|
|
|
|
|
2000-06-28 05:33:33 +02:00
|
|
|
MemoryContextSwitchTo(oldcontext);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* PerformPortalClose
|
1996-07-09 08:22:35 +02:00
|
|
|
* --------------------------------
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
PerformPortalClose(char *name, CommandDest dest)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Portal portal;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* sanity checks
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
if (name == NULL)
|
|
|
|
{
|
2000-06-28 05:33:33 +02:00
|
|
|
elog(NOTICE, "PerformPortalClose: missing portal name");
|
1997-09-07 07:04:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get the portal from the portal name
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
portal = GetPortalByName(name);
|
|
|
|
if (!PortalIsValid(portal))
|
|
|
|
{
|
|
|
|
elog(NOTICE, "PerformPortalClose: portal \"%s\" not found",
|
|
|
|
name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Note: PortalCleanup is called as a side-effect
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2001-10-05 19:28:13 +02:00
|
|
|
PortalDrop(portal);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------
|
2000-04-12 19:17:23 +02:00
|
|
|
* AlterTableAddColumn
|
2000-01-16 21:05:00 +01:00
|
|
|
* (formerly known as PerformAddAttribute)
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* adds an additional attribute to a relation
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Adds attribute field(s) to a relation. Each new attribute
|
|
|
|
* is given attnums in sequential order and is added to the
|
|
|
|
* ATTRIBUTE relation. If the AMI fails, defunct tuples will
|
|
|
|
* remain in the ATTRIBUTE relation for later vacuuming.
|
|
|
|
* Later, there may be some reserved attribute names???
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* (If needed, can instead use elog to handle exceptions.)
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Note:
|
|
|
|
* Initial idea of ordering the tuple attributes so that all
|
|
|
|
* the variable length domains occured last was scratched. Doing
|
|
|
|
* so would not speed access too much (in general) and would create
|
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
|
|
|
* many complications in formtuple, heap_getattr, and addattribute.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* scan attribute catalog for name conflict (within rel)
|
|
|
|
* scan type catalog for absence of data type (if not arg)
|
|
|
|
* create attnum magically???
|
|
|
|
* create attribute tuple
|
|
|
|
* insert attribute in attribute catalog
|
|
|
|
* modify reldesc
|
|
|
|
* create new relation tuple
|
|
|
|
* insert new relation in relation catalog
|
|
|
|
* delete original relation from relation catalog
|
1996-07-09 08:22:35 +02:00
|
|
|
* ----------------
|
|
|
|
*/
|
|
|
|
void
|
2000-01-16 21:05:00 +01:00
|
|
|
AlterTableAddColumn(const char *relationName,
|
1997-09-07 07:04:48 +02:00
|
|
|
bool inherits,
|
1997-09-08 23:56:23 +02:00
|
|
|
ColumnDef *colDef)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
1998-09-01 05:29:17 +02:00
|
|
|
Relation rel,
|
1997-09-08 04:41:22 +02:00
|
|
|
attrdesc;
|
1999-09-18 21:08:25 +02:00
|
|
|
Oid myrelid;
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple reltup;
|
2000-11-16 23:30:52 +01:00
|
|
|
HeapTuple newreltup;
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple attributeTuple;
|
1998-09-01 05:29:17 +02:00
|
|
|
Form_pg_attribute attribute;
|
1997-09-07 07:04:48 +02:00
|
|
|
FormData_pg_attribute attributeD;
|
1997-09-08 04:41:22 +02:00
|
|
|
int i;
|
|
|
|
int minattnum,
|
|
|
|
maxatts;
|
2001-10-12 02:07:15 +02:00
|
|
|
HeapTuple typeTuple;
|
|
|
|
Form_pg_type tform;
|
|
|
|
char *typename;
|
|
|
|
int attndims;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* permissions checking. this would normally be done in utility.c,
|
|
|
|
* but this particular routine is recursive.
|
|
|
|
*
|
|
|
|
* normally, only the owner of a class can change its schema.
|
|
|
|
*/
|
1999-03-17 23:53:31 +01:00
|
|
|
if (!allowSystemTableMods && IsSystemRelationName(relationName))
|
2000-01-16 21:05:00 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
1997-09-07 07:04:48 +02:00
|
|
|
relationName);
|
2000-09-06 16:15:31 +02:00
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
2000-01-16 21:05:00 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Grab an exclusive lock on the target table, which we will NOT
|
|
|
|
* release until end of transaction.
|
1999-09-18 21:08:25 +02:00
|
|
|
*/
|
2000-01-22 15:20:56 +01:00
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2001-01-07 01:05:22 +01:00
|
|
|
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
|
|
|
relationName);
|
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
heap_close(rel, NoLock); /* close rel but keep lock! */
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
2001-10-12 02:07:15 +02:00
|
|
|
* Recurse to add the column to child classes, if requested.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
|
|
|
* any permissions or problems with duplicate attributes will cause the
|
|
|
|
* whole transaction to abort, which is what we want -- all or
|
|
|
|
* nothing.
|
|
|
|
*/
|
2001-10-12 02:07:15 +02:00
|
|
|
if (inherits)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2001-10-12 02:07:15 +02:00
|
|
|
List *child,
|
|
|
|
*children;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
/* this routine is actually in the planner */
|
|
|
|
children = find_all_inheritors(myrelid);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
/*
|
|
|
|
* find_all_inheritors does the recursive search of the
|
2001-10-25 07:50:21 +02:00
|
|
|
* inheritance hierarchy, so all we have to do is process all of
|
|
|
|
* the relids in the list that it returns.
|
2001-10-12 02:07:15 +02:00
|
|
|
*/
|
|
|
|
foreach(child, children)
|
|
|
|
{
|
|
|
|
Oid childrelid = lfirsti(child);
|
|
|
|
char *childrelname;
|
1999-12-14 04:35:28 +01:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
if (childrelid == myrelid)
|
|
|
|
continue;
|
|
|
|
rel = heap_open(childrelid, AccessExclusiveLock);
|
|
|
|
childrelname = pstrdup(RelationGetRelationName(rel));
|
|
|
|
heap_close(rel, AccessExclusiveLock);
|
2000-07-05 15:50:59 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
AlterTableAddColumn(childrelname, false, colDef);
|
2000-07-05 15:50:59 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
pfree(childrelname);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
/*
|
|
|
|
* OK, get on with it...
|
|
|
|
*
|
|
|
|
* Implementation restrictions: because we don't touch the table rows,
|
|
|
|
* the new column values will initially appear to be NULLs. (This
|
|
|
|
* happens because the heap tuple access routines always check for
|
2001-10-25 07:50:21 +02:00
|
|
|
* attnum > # of attributes in tuple, and return NULL if so.)
|
|
|
|
* Therefore we can't support a DEFAULT value in SQL92-compliant
|
|
|
|
* fashion, and we also can't allow a NOT NULL constraint.
|
2001-10-12 02:07:15 +02:00
|
|
|
*
|
2001-10-25 07:50:21 +02:00
|
|
|
* We do allow CHECK constraints, even though these theoretically could
|
|
|
|
* fail for NULL rows (eg, CHECK (newcol IS NOT NULL)).
|
2001-10-12 02:07:15 +02:00
|
|
|
*/
|
|
|
|
if (colDef->raw_default || colDef->cooked_default)
|
|
|
|
elog(ERROR, "Adding columns with defaults is not implemented."
|
|
|
|
"\n\tAdd the column, then use ALTER TABLE SET DEFAULT.");
|
|
|
|
|
|
|
|
if (colDef->is_not_null)
|
|
|
|
elog(ERROR, "Adding NOT NULL columns is not implemented."
|
|
|
|
"\n\tAdd the column, then use ALTER TABLE ADD CONSTRAINT.");
|
|
|
|
|
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
rel = heap_openr(RelationRelationName, RowExclusiveLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
reltup = SearchSysCache(RELNAME,
|
|
|
|
PointerGetDatum(relationName),
|
|
|
|
0, 0, 0);
|
1998-08-19 04:04:17 +02:00
|
|
|
|
|
|
|
if (!HeapTupleIsValid(reltup))
|
2000-01-16 21:05:00 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
|
1997-09-07 07:04:48 +02:00
|
|
|
relationName);
|
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
if (SearchSysCacheExists(ATTNAME,
|
|
|
|
ObjectIdGetDatum(reltup->t_data->t_oid),
|
|
|
|
PointerGetDatum(colDef->colname),
|
|
|
|
0, 0))
|
|
|
|
elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"",
|
|
|
|
colDef->colname, relationName);
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
|
|
|
|
maxatts = minattnum + 1;
|
|
|
|
if (maxatts > MaxHeapAttributeNumber)
|
2000-01-16 21:05:00 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: relations limited to %d columns",
|
1997-09-07 07:04:48 +02:00
|
|
|
MaxHeapAttributeNumber);
|
2001-10-12 02:07:15 +02:00
|
|
|
i = minattnum + 1;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
if (colDef->typename->arrayBounds)
|
|
|
|
{
|
|
|
|
attndims = length(colDef->typename->arrayBounds);
|
|
|
|
typename = makeArrayTypeName(colDef->typename->name);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
attndims = 0;
|
|
|
|
typename = colDef->typename->name;
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
typeTuple = SearchSysCache(TYPENAME,
|
|
|
|
PointerGetDatum(typename),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(typeTuple))
|
|
|
|
elog(ERROR, "ALTER TABLE: type \"%s\" does not exist", typename);
|
|
|
|
tform = (Form_pg_type) GETSTRUCT(typeTuple);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
attributeTuple = heap_addheader(Natts_pg_attribute,
|
2001-06-12 07:55:50 +02:00
|
|
|
ATTRIBUTE_TUPLE_SIZE,
|
|
|
|
(void *) &attributeD);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
attribute->attrelid = reltup->t_data->t_oid;
|
|
|
|
namestrcpy(&(attribute->attname), colDef->colname);
|
|
|
|
attribute->atttypid = typeTuple->t_data->t_oid;
|
|
|
|
attribute->attstattarget = DEFAULT_ATTSTATTARGET;
|
|
|
|
attribute->attlen = tform->typlen;
|
|
|
|
attribute->attcacheoff = -1;
|
|
|
|
attribute->atttypmod = colDef->typename->typmod;
|
|
|
|
attribute->attnum = i;
|
|
|
|
attribute->attbyval = tform->typbyval;
|
|
|
|
attribute->attndims = attndims;
|
|
|
|
attribute->attisset = (bool) (tform->typtype == 'c');
|
|
|
|
attribute->attstorage = tform->typstorage;
|
|
|
|
attribute->attalign = tform->typalign;
|
|
|
|
attribute->attnotnull = colDef->is_not_null;
|
|
|
|
attribute->atthasdef = (colDef->raw_default != NULL ||
|
|
|
|
colDef->cooked_default != NULL);
|
|
|
|
|
|
|
|
ReleaseSysCache(typeTuple);
|
|
|
|
|
|
|
|
heap_insert(attrdesc, attributeTuple);
|
|
|
|
|
|
|
|
/* Update indexes on pg_attribute */
|
|
|
|
if (RelationGetForm(attrdesc)->relhasindex)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2001-10-12 02:07:15 +02:00
|
|
|
Relation idescs[Num_pg_attr_indices];
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
|
|
|
|
CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple);
|
1997-09-07 07:04:48 +02:00
|
|
|
CatalogCloseIndices(Num_pg_attr_indices, idescs);
|
2001-10-12 02:07:15 +02:00
|
|
|
}
|
1999-09-18 21:08:25 +02:00
|
|
|
|
2001-06-12 07:55:50 +02:00
|
|
|
heap_close(attrdesc, NoLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
/*
|
|
|
|
* Update number of attributes in pg_class tuple
|
|
|
|
*/
|
|
|
|
newreltup = heap_copytuple(reltup);
|
|
|
|
|
|
|
|
((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts;
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(rel, &newreltup->t_self, newreltup);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* keep catalog indices current */
|
2001-10-12 02:07:15 +02:00
|
|
|
if (RelationGetForm(rel)->relhasindex)
|
|
|
|
{
|
|
|
|
Relation ridescs[Num_pg_class_indices];
|
|
|
|
|
|
|
|
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
|
|
|
|
CatalogIndexInsert(ridescs, Num_pg_class_indices, rel, newreltup);
|
|
|
|
CatalogCloseIndices(Num_pg_class_indices, ridescs);
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
heap_freetuple(newreltup);
|
|
|
|
ReleaseSysCache(reltup);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
heap_close(rel, NoLock);
|
2000-07-05 15:22:25 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
/*
|
|
|
|
* Make our catalog updates visible for subsequent steps.
|
|
|
|
*/
|
|
|
|
CommandCounterIncrement();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add any CHECK constraints attached to the new column.
|
|
|
|
*
|
2001-10-25 07:50:21 +02:00
|
|
|
* To do this we must re-open the rel so that its new attr list gets
|
|
|
|
* loaded into the relcache.
|
2001-10-12 02:07:15 +02:00
|
|
|
*/
|
|
|
|
if (colDef->constraints != NIL)
|
|
|
|
{
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
|
|
|
AddRelationRawConstraints(rel, NIL, colDef->constraints);
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
}
|
|
|
|
|
2000-07-05 15:22:25 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* Automatically create the secondary relation for TOAST if it
|
|
|
|
* formerly had no such but now has toastable attributes.
|
2000-07-05 15:22:25 +02:00
|
|
|
*/
|
|
|
|
AlterTableCreateToastTable(relationName, true);
|
2000-01-16 21:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
|
|
|
|
*/
|
|
|
|
void
|
2001-05-07 02:43:27 +02:00
|
|
|
AlterTableAlterColumnDefault(const char *relationName,
|
|
|
|
bool inh, const char *colName,
|
|
|
|
Node *newDefault)
|
2000-01-16 21:05:00 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation rel;
|
|
|
|
HeapTuple tuple;
|
|
|
|
int16 attnum;
|
|
|
|
Oid myrelid;
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
if (!allowSystemTableMods && IsSystemRelationName(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
|
|
|
relationName);
|
|
|
|
#ifndef NO_SECURITY
|
2000-09-06 16:15:31 +02:00
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
2000-01-16 21:05:00 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2001-11-02 17:30:29 +01:00
|
|
|
|
2001-01-07 01:05:22 +01:00
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
|
|
|
relationName);
|
2001-11-02 17:30:29 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
heap_close(rel, NoLock);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
/*
|
|
|
|
* Propagate to children if desired
|
|
|
|
*/
|
2000-01-16 21:05:00 +01:00
|
|
|
if (inh)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
|
|
|
List *child,
|
|
|
|
*children;
|
|
|
|
|
|
|
|
/* this routine is actually in the planner */
|
|
|
|
children = find_all_inheritors(myrelid);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_all_inheritors does the recursive search of the
|
|
|
|
* inheritance hierarchy, so all we have to do is process all of
|
|
|
|
* the relids in the list that it returns.
|
|
|
|
*/
|
|
|
|
foreach(child, children)
|
2000-01-16 21:05:00 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
Oid childrelid = lfirsti(child);
|
|
|
|
|
|
|
|
if (childrelid == myrelid)
|
|
|
|
continue;
|
|
|
|
rel = heap_open(childrelid, AccessExclusiveLock);
|
2001-05-07 02:43:27 +02:00
|
|
|
AlterTableAlterColumnDefault(RelationGetRelationName(rel),
|
|
|
|
false, colName, newDefault);
|
2000-04-12 19:17:23 +02:00
|
|
|
heap_close(rel, AccessExclusiveLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -= now do the thing on this relation =- */
|
|
|
|
|
|
|
|
/* reopen the business */
|
2001-05-07 02:43:27 +02:00
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* get the number of the attribute
|
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
tuple = SearchSysCache(ATTNAME,
|
|
|
|
ObjectIdGetDatum(myrelid),
|
|
|
|
PointerGetDatum(colName),
|
|
|
|
0, 0);
|
2000-04-12 19:17:23 +02:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
|
|
|
|
relationName, colName);
|
|
|
|
|
|
|
|
attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(tuple);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2001-06-12 07:55:50 +02:00
|
|
|
if (newDefault)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2001-06-12 07:55:50 +02:00
|
|
|
/* SET DEFAULT */
|
2000-04-12 19:17:23 +02:00
|
|
|
RawColumnDefault *rawEnt;
|
|
|
|
|
|
|
|
/* Get rid of the old one first */
|
|
|
|
drop_default(myrelid, attnum);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
|
|
|
|
rawEnt->attnum = attnum;
|
2000-04-12 19:17:23 +02:00
|
|
|
rawEnt->raw_default = newDefault;
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
/*
|
|
|
|
* This function is intended for CREATE TABLE, so it processes a
|
|
|
|
* _list_ of defaults, but we just do one.
|
|
|
|
*/
|
2001-10-12 02:07:15 +02:00
|
|
|
AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2001-06-12 07:55:50 +02:00
|
|
|
/* DROP DEFAULT */
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation attr_rel;
|
|
|
|
ScanKeyData scankeys[3];
|
|
|
|
HeapScanDesc scan;
|
|
|
|
|
2001-06-12 07:55:50 +02:00
|
|
|
attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
|
2000-05-28 19:56:29 +02:00
|
|
|
ScanKeyEntryInitialize(&scankeys[0], 0x0,
|
|
|
|
Anum_pg_attribute_attrelid, F_OIDEQ,
|
2000-04-12 19:17:23 +02:00
|
|
|
ObjectIdGetDatum(myrelid));
|
2000-05-28 19:56:29 +02:00
|
|
|
ScanKeyEntryInitialize(&scankeys[1], 0x0,
|
|
|
|
Anum_pg_attribute_attnum, F_INT2EQ,
|
2000-04-12 19:17:23 +02:00
|
|
|
Int16GetDatum(attnum));
|
2000-05-28 19:56:29 +02:00
|
|
|
ScanKeyEntryInitialize(&scankeys[2], 0x0,
|
|
|
|
Anum_pg_attribute_atthasdef, F_BOOLEQ,
|
2000-08-21 19:22:36 +02:00
|
|
|
BoolGetDatum(true));
|
2000-04-12 19:17:23 +02:00
|
|
|
|
|
|
|
scan = heap_beginscan(attr_rel, false, SnapshotNow, 3, scankeys);
|
|
|
|
AssertState(scan != NULL);
|
|
|
|
|
|
|
|
if (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
HeapTuple newtuple;
|
|
|
|
Relation irelations[Num_pg_attr_indices];
|
|
|
|
|
|
|
|
/* update to false */
|
|
|
|
newtuple = heap_copytuple(tuple);
|
|
|
|
((Form_pg_attribute) GETSTRUCT(newtuple))->atthasdef = FALSE;
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(attr_rel, &tuple->t_self, newtuple);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
|
|
|
/* keep the system catalog indices current */
|
|
|
|
CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
|
|
|
|
CatalogIndexInsert(irelations, Num_pg_attr_indices, attr_rel, newtuple);
|
|
|
|
CatalogCloseIndices(Num_pg_attr_indices, irelations);
|
|
|
|
|
|
|
|
/* get rid of actual default definition */
|
|
|
|
drop_default(myrelid, attnum);
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(attr_rel, NoLock);
|
|
|
|
}
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
drop_default(Oid relid, int16 attnum)
|
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
ScanKeyData scankeys[2];
|
|
|
|
HeapScanDesc scan;
|
|
|
|
Relation attrdef_rel;
|
|
|
|
HeapTuple tuple;
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2001-06-12 07:55:50 +02:00
|
|
|
attrdef_rel = heap_openr(AttrDefaultRelationName, RowExclusiveLock);
|
2000-05-28 19:56:29 +02:00
|
|
|
ScanKeyEntryInitialize(&scankeys[0], 0x0,
|
|
|
|
Anum_pg_attrdef_adrelid, F_OIDEQ,
|
2000-04-12 19:17:23 +02:00
|
|
|
ObjectIdGetDatum(relid));
|
2000-05-28 19:56:29 +02:00
|
|
|
ScanKeyEntryInitialize(&scankeys[1], 0x0,
|
|
|
|
Anum_pg_attrdef_adnum, F_INT2EQ,
|
2000-04-12 19:17:23 +02:00
|
|
|
Int16GetDatum(attnum));
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
scan = heap_beginscan(attrdef_rel, false, SnapshotNow, 2, scankeys);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
if (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_delete(attrdef_rel, &tuple->t_self);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
heap_endscan(scan);
|
2000-01-16 21:05:00 +01:00
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
heap_close(attrdef_rel, NoLock);
|
2000-01-16 21:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
/*
|
2002-03-05 06:33:31 +01:00
|
|
|
* ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE
|
2001-05-07 02:43:27 +02:00
|
|
|
*/
|
|
|
|
void
|
2002-03-05 06:33:31 +01:00
|
|
|
AlterTableAlterColumnFlags(const char *relationName,
|
2001-05-07 02:43:27 +02:00
|
|
|
bool inh, const char *colName,
|
2002-03-05 06:33:31 +01:00
|
|
|
Node *flagValue, const char *flagType)
|
2001-05-07 02:43:27 +02:00
|
|
|
{
|
|
|
|
Relation rel;
|
|
|
|
Oid myrelid;
|
2002-03-05 06:33:31 +01:00
|
|
|
int newtarget = 1;
|
|
|
|
char newstorage = 'x';
|
|
|
|
char *storagemode;
|
2001-05-07 02:43:27 +02:00
|
|
|
Relation attrelation;
|
|
|
|
HeapTuple tuple;
|
|
|
|
|
2002-03-05 06:33:31 +01:00
|
|
|
/* we allow statistics case for system tables */
|
|
|
|
|
|
|
|
if (*flagType =='M' && !allowSystemTableMods && IsSystemRelationName(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
|
|
|
relationName);
|
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
#ifndef NO_SECURITY
|
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2001-11-02 17:30:29 +01:00
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
|
|
|
relationName);
|
2001-11-02 17:30:29 +01:00
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
heap_close(rel, NoLock); /* close rel, but keep lock! */
|
|
|
|
|
2002-03-05 06:33:31 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the supplied parameters before anything else
|
|
|
|
*/
|
|
|
|
if (*flagType == 'S') /*
|
|
|
|
* STATISTICS
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
Assert(IsA(flagValue, Integer));
|
|
|
|
newtarget = intVal(flagValue);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Limit target to sane range (should we raise an error instead?)
|
|
|
|
*/
|
|
|
|
if (newtarget < 0)
|
|
|
|
newtarget = 0;
|
|
|
|
else if (newtarget > 1000)
|
|
|
|
newtarget = 1000;
|
|
|
|
}
|
|
|
|
else if (*flagType == 'M') /*
|
|
|
|
* STORAGE
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
Assert(IsA(flagValue, Value));
|
|
|
|
|
|
|
|
storagemode = strVal(flagValue);
|
|
|
|
if (strcasecmp(storagemode, "plain") == 0)
|
|
|
|
newstorage = 'p';
|
|
|
|
else if (strcasecmp(storagemode, "external") == 0)
|
|
|
|
newstorage = 'e';
|
|
|
|
else if (strcasecmp(storagemode, "extended") == 0)
|
|
|
|
newstorage = 'x';
|
|
|
|
else if (strcasecmp(storagemode, "main") == 0)
|
|
|
|
newstorage = 'm';
|
|
|
|
else
|
|
|
|
elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized",
|
|
|
|
storagemode);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
elog(ERROR, "ALTER TABLE: Invalid column flag: %c",
|
|
|
|
(int) *flagType);
|
|
|
|
}
|
|
|
|
|
2001-05-07 02:43:27 +02:00
|
|
|
/*
|
|
|
|
* Propagate to children if desired
|
|
|
|
*/
|
|
|
|
if (inh)
|
|
|
|
{
|
|
|
|
List *child,
|
|
|
|
*children;
|
|
|
|
|
|
|
|
/* this routine is actually in the planner */
|
|
|
|
children = find_all_inheritors(myrelid);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_all_inheritors does the recursive search of the
|
|
|
|
* inheritance hierarchy, so all we have to do is process all of
|
|
|
|
* the relids in the list that it returns.
|
|
|
|
*/
|
|
|
|
foreach(child, children)
|
|
|
|
{
|
|
|
|
Oid childrelid = lfirsti(child);
|
|
|
|
|
|
|
|
if (childrelid == myrelid)
|
|
|
|
continue;
|
|
|
|
rel = heap_open(childrelid, AccessExclusiveLock);
|
2002-03-05 06:33:31 +01:00
|
|
|
AlterTableAlterColumnFlags(RelationGetRelationName(rel),
|
|
|
|
false, colName, flagValue, flagType);
|
2001-05-07 02:43:27 +02:00
|
|
|
heap_close(rel, AccessExclusiveLock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -= now do the thing on this relation =- */
|
|
|
|
|
|
|
|
attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
tuple = SearchSysCacheCopy(ATTNAME,
|
|
|
|
ObjectIdGetDatum(myrelid),
|
|
|
|
PointerGetDatum(colName),
|
|
|
|
0, 0);
|
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
|
|
|
|
relationName, colName);
|
|
|
|
|
|
|
|
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnum < 0)
|
|
|
|
elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"",
|
|
|
|
colName);
|
2002-03-05 06:33:31 +01:00
|
|
|
/*
|
|
|
|
* Now change the appropriate field
|
|
|
|
*/
|
|
|
|
if (*flagType == 'S')
|
|
|
|
((Form_pg_attribute) GETSTRUCT(tuple))->attstattarget = newtarget;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ((newstorage == 'p') ||
|
|
|
|
(((Form_pg_attribute) GETSTRUCT(tuple))->attlen == -1))
|
|
|
|
((Form_pg_attribute) GETSTRUCT(tuple))->attstorage = newstorage;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
elog(ERROR,
|
|
|
|
"ALTER TABLE: Fixed-length columns can only have storage \"plain\"");
|
|
|
|
}
|
|
|
|
}
|
2001-05-07 02:43:27 +02:00
|
|
|
simple_heap_update(attrelation, &tuple->t_self, tuple);
|
|
|
|
|
|
|
|
/* keep system catalog indices current */
|
|
|
|
{
|
|
|
|
Relation irelations[Num_pg_attr_indices];
|
|
|
|
|
|
|
|
CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
|
|
|
|
CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple);
|
|
|
|
CatalogCloseIndices(Num_pg_attr_indices, irelations);
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_freetuple(tuple);
|
2001-06-12 07:55:50 +02:00
|
|
|
heap_close(attrelation, NoLock);
|
2001-05-07 02:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
#ifdef _DROP_COLUMN_HACK__
|
|
|
|
/*
|
|
|
|
* ALTER TABLE DROP COLUMN trial implementation
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find a specified attribute in a node entry
|
|
|
|
*/
|
|
|
|
static bool
|
2000-09-12 23:07:18 +02:00
|
|
|
find_attribute_walker(Node *node, int *attnump)
|
2000-03-09 06:00:26 +01:00
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
if (IsA(node, Var))
|
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
Var *var = (Var *) node;
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
if (var->varlevelsup == 0 && var->varno == 1 &&
|
2000-09-12 23:07:18 +02:00
|
|
|
var->varattno == *attnump)
|
2000-03-09 06:00:26 +01:00
|
|
|
return true;
|
|
|
|
}
|
2000-09-12 23:07:18 +02:00
|
|
|
return expression_tree_walker(node, find_attribute_walker,
|
|
|
|
(void *) attnump);
|
2000-03-09 06:00:26 +01:00
|
|
|
}
|
2000-08-25 20:05:54 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
static bool
|
|
|
|
find_attribute_in_node(Node *node, int attnum)
|
|
|
|
{
|
2000-09-12 23:07:18 +02:00
|
|
|
return find_attribute_walker(node, &attnum);
|
2000-03-09 06:00:26 +01:00
|
|
|
}
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
/*
|
|
|
|
* Remove/check references for the column
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
RemoveColumnReferences(Oid reloid, int attnum, bool checkonly, HeapTuple reltup)
|
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation indexRelation,
|
|
|
|
rcrel;
|
|
|
|
ScanKeyData entry;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
void *sysscan;
|
|
|
|
HeapTuple htup,
|
|
|
|
indexTuple;
|
|
|
|
Form_pg_index index;
|
|
|
|
Form_pg_class pgcform = (Form_pg_class) NULL;
|
|
|
|
int i;
|
2000-03-09 06:00:26 +01:00
|
|
|
bool checkok = true;
|
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
if (!checkonly)
|
2000-04-12 19:17:23 +02:00
|
|
|
pgcform = (Form_pg_class) GETSTRUCT(reltup);
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Remove/check constraints here
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
2002-02-19 21:11:20 +01:00
|
|
|
ScanKeyEntryInitialize(&entry, (bits16) 0x0,
|
|
|
|
Anum_pg_relcheck_rcrelid,
|
|
|
|
(RegProcedure) F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(reloid));
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
rcrel = heap_openr(RelCheckRelationName, RowExclusiveLock);
|
2002-02-19 21:11:20 +01:00
|
|
|
sysscan = systable_beginscan(rcrel, RelCheckIndex, true,
|
|
|
|
SnapshotNow,
|
|
|
|
1, &entry);
|
2000-03-09 06:00:26 +01:00
|
|
|
|
|
|
|
while (HeapTupleIsValid(htup = systable_getnext(sysscan)))
|
|
|
|
{
|
2000-07-06 01:12:09 +02:00
|
|
|
Form_pg_relcheck relcheck;
|
2000-04-12 19:17:23 +02:00
|
|
|
char *ccbin;
|
|
|
|
Node *node;
|
2000-03-09 06:00:26 +01:00
|
|
|
|
|
|
|
relcheck = (Form_pg_relcheck) GETSTRUCT(htup);
|
2000-07-06 01:12:09 +02:00
|
|
|
ccbin = DatumGetCString(DirectFunctionCall1(textout,
|
2001-03-22 05:01:46 +01:00
|
|
|
PointerGetDatum(&relcheck->rcbin)));
|
2000-03-09 06:00:26 +01:00
|
|
|
node = stringToNode(ccbin);
|
|
|
|
pfree(ccbin);
|
|
|
|
if (find_attribute_in_node(node, attnum))
|
|
|
|
{
|
|
|
|
if (checkonly)
|
|
|
|
{
|
|
|
|
checkok = false;
|
|
|
|
elog(ERROR, "target column is used in a constraint");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_delete(rcrel, &htup->t_self);
|
2000-03-09 06:00:26 +01:00
|
|
|
pgcform->relchecks--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2002-02-19 21:11:20 +01:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
systable_endscan(sysscan);
|
|
|
|
heap_close(rcrel, NoLock);
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* What to do with triggers/rules/views/procedues ?
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Remove/check indexes
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
indexRelation = heap_openr(IndexRelationName, RowExclusiveLock);
|
|
|
|
ScanKeyEntryInitialize(&entry, 0, Anum_pg_index_indrelid, F_OIDEQ,
|
2000-04-12 19:17:23 +02:00
|
|
|
ObjectIdGetDatum(reloid));
|
2000-03-09 06:00:26 +01:00
|
|
|
scan = heap_beginscan(indexRelation, false, SnapshotNow, 1, &entry);
|
|
|
|
while (HeapTupleIsValid(indexTuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
index = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
|
|
for (i = 0; i < INDEX_MAX_KEYS; i++)
|
|
|
|
{
|
|
|
|
if (index->indkey[i] == InvalidAttrNumber)
|
|
|
|
break;
|
|
|
|
else if (index->indkey[i] == attnum)
|
|
|
|
{
|
|
|
|
if (checkonly)
|
|
|
|
{
|
|
|
|
checkok = false;
|
|
|
|
elog(ERROR, "target column is used in an index");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2000-11-16 23:30:52 +01:00
|
|
|
htup = SearchSysCache(RELOID,
|
2001-03-22 05:01:46 +01:00
|
|
|
ObjectIdGetDatum(index->indexrelid),
|
2000-11-16 23:30:52 +01:00
|
|
|
0, 0, 0);
|
2000-03-09 06:00:26 +01:00
|
|
|
RemoveIndex(NameStr(((Form_pg_class) GETSTRUCT(htup))->relname));
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(htup);
|
2000-03-09 06:00:26 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
heap_close(indexRelation, NoLock);
|
|
|
|
|
|
|
|
return checkok;
|
|
|
|
}
|
2001-11-05 18:46:40 +01:00
|
|
|
#endif /* _DROP_COLUMN_HACK__ */
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* ALTER TABLE DROP COLUMN
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
AlterTableDropColumn(const char *relationName,
|
2000-04-12 19:17:23 +02:00
|
|
|
bool inh, const char *colName,
|
|
|
|
int behavior)
|
2000-01-16 21:05:00 +01:00
|
|
|
{
|
2000-03-09 06:00:26 +01:00
|
|
|
#ifdef _DROP_COLUMN_HACK__
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation rel,
|
2001-06-12 07:55:50 +02:00
|
|
|
attrdesc;
|
2001-08-10 20:57:42 +02:00
|
|
|
Oid myrelid;
|
2000-03-09 06:00:26 +01:00
|
|
|
HeapTuple reltup;
|
2000-04-12 19:17:23 +02:00
|
|
|
HeapTupleData classtuple;
|
2000-03-09 06:00:26 +01:00
|
|
|
Buffer buffer;
|
|
|
|
Form_pg_attribute attribute;
|
|
|
|
HeapTuple tup;
|
|
|
|
Relation idescs[Num_pg_attr_indices];
|
2000-04-12 19:17:23 +02:00
|
|
|
int attnum;
|
2000-03-09 06:00:26 +01:00
|
|
|
bool hasindex;
|
|
|
|
char dropColname[32];
|
|
|
|
|
2000-04-12 19:17:23 +02:00
|
|
|
if (inh)
|
2000-03-09 06:00:26 +01:00
|
|
|
elog(ERROR, "ALTER TABLE / DROP COLUMN with inherit option is not supported yet");
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
/*
|
|
|
|
* permissions checking. this would normally be done in utility.c,
|
|
|
|
* but this particular routine is recursive.
|
|
|
|
*
|
|
|
|
* normally, only the owner of a class can change its schema.
|
|
|
|
*/
|
|
|
|
if (!allowSystemTableMods && IsSystemRelationName(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
|
|
|
relationName);
|
|
|
|
#ifndef NO_SECURITY
|
2000-09-06 16:15:31 +02:00
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
2000-03-09 06:00:26 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Grab an exclusive lock on the target table, which we will NOT
|
|
|
|
* release until end of transaction.
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2001-01-07 01:05:22 +01:00
|
|
|
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
|
|
|
relationName);
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
heap_close(rel, NoLock); /* close rel but keep lock! */
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* What to do when rel has inheritors ?
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
if (length(find_all_inheritors(myrelid)) > 1)
|
|
|
|
elog(ERROR, "ALTER TABLE: cannot drop a column on table that is inherited from");
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* lock the pg_class tuple for update
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
rel = heap_openr(RelationRelationName, RowExclusiveLock);
|
|
|
|
reltup = SearchSysCache(RELNAME,
|
|
|
|
PointerGetDatum(relationName),
|
|
|
|
0, 0, 0);
|
2000-03-09 06:00:26 +01:00
|
|
|
if (!HeapTupleIsValid(reltup))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
|
|
|
|
relationName);
|
|
|
|
classtuple.t_self = reltup->t_self;
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(reltup);
|
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
switch (heap_mark4update(rel, &classtuple, &buffer))
|
|
|
|
{
|
|
|
|
case HeapTupleSelfUpdated:
|
|
|
|
case HeapTupleMayBeUpdated:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "couldn't lock pg_class tuple");
|
|
|
|
}
|
|
|
|
reltup = heap_copytuple(&classtuple);
|
|
|
|
ReleaseBuffer(buffer);
|
|
|
|
|
|
|
|
attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
/*
|
2000-11-16 23:30:52 +01:00
|
|
|
* Get the target pg_attribute tuple and make a modifiable copy
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
tup = SearchSysCacheCopy(ATTNAME,
|
|
|
|
ObjectIdGetDatum(reltup->t_data->t_oid),
|
|
|
|
PointerGetDatum(colName),
|
|
|
|
0, 0);
|
2000-03-09 06:00:26 +01:00
|
|
|
if (!HeapTupleIsValid(tup))
|
|
|
|
elog(ERROR, "ALTER TABLE: column name \"%s\" doesn't exist in table \"%s\"",
|
2000-04-12 19:17:23 +02:00
|
|
|
colName, relationName);
|
2000-03-09 06:00:26 +01:00
|
|
|
|
|
|
|
attribute = (Form_pg_attribute) GETSTRUCT(tup);
|
|
|
|
attnum = attribute->attnum;
|
2000-11-16 23:30:52 +01:00
|
|
|
if (attnum <= 0)
|
|
|
|
elog(ERROR, "ALTER TABLE: column name \"%s\" was already dropped",
|
|
|
|
colName);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Check constraints/indices etc here
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
if (behavior != CASCADE)
|
|
|
|
{
|
|
|
|
if (!RemoveColumnReferences(myrelid, attnum, true, NULL))
|
|
|
|
elog(ERROR, "the column is referenced");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* change the target pg_attribute tuple
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
sprintf(dropColname, "*already Dropped*%d", attnum);
|
|
|
|
namestrcpy(&(attribute->attname), dropColname);
|
|
|
|
ATTRIBUTE_DROP_COLUMN(attribute);
|
|
|
|
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(attrdesc, &tup->t_self, tup);
|
2000-03-09 06:00:26 +01:00
|
|
|
hasindex = (!IsIgnoringSystemIndexes() && RelationGetForm(attrdesc)->relhasindex);
|
|
|
|
if (hasindex)
|
|
|
|
{
|
|
|
|
CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
|
|
|
|
CatalogIndexInsert(idescs, Num_pg_attr_indices,
|
2000-04-12 19:17:23 +02:00
|
|
|
attrdesc, tup);
|
2000-03-09 06:00:26 +01:00
|
|
|
CatalogCloseIndices(Num_pg_attr_indices, idescs);
|
|
|
|
}
|
|
|
|
heap_close(attrdesc, NoLock);
|
|
|
|
heap_freetuple(tup);
|
|
|
|
|
2001-08-10 20:57:42 +02:00
|
|
|
/* delete comment for this attribute only */
|
|
|
|
CreateComments(RelationGetRelid(rel), RelOid_pg_class,
|
|
|
|
(int32) attnum, NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2001-06-12 07:55:50 +02:00
|
|
|
/* delete attrdef */
|
|
|
|
drop_default(myrelid, attnum);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-03-09 06:00:26 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Remove objects which reference this column
|
2000-03-09 06:00:26 +01:00
|
|
|
*/
|
|
|
|
if (behavior == CASCADE)
|
|
|
|
{
|
|
|
|
Relation ridescs[Num_pg_class_indices];
|
|
|
|
|
|
|
|
RemoveColumnReferences(myrelid, attnum, false, reltup);
|
|
|
|
/* update pg_class tuple */
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(rel, &reltup->t_self, reltup);
|
2000-03-09 06:00:26 +01:00
|
|
|
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
|
|
|
|
CatalogIndexInsert(ridescs, Num_pg_class_indices, rel, reltup);
|
|
|
|
CatalogCloseIndices(Num_pg_class_indices, ridescs);
|
|
|
|
}
|
|
|
|
|
|
|
|
heap_freetuple(reltup);
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
#else
|
2001-10-25 07:50:21 +02:00
|
|
|
elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented");
|
2001-11-05 18:46:40 +01:00
|
|
|
#endif /* _DROP_COLUMN_HACK__ */
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1998-12-18 10:10:39 +01:00
|
|
|
|
2000-01-16 21:05:00 +01:00
|
|
|
|
|
|
|
|
2000-01-22 15:20:56 +01:00
|
|
|
/*
|
|
|
|
* ALTER TABLE ADD CONSTRAINT
|
|
|
|
*/
|
2000-01-16 21:05:00 +01:00
|
|
|
void
|
2000-07-18 05:57:33 +02:00
|
|
|
AlterTableAddConstraint(char *relationName,
|
2001-10-12 02:07:15 +02:00
|
|
|
bool inh, List *newConstraints)
|
2000-01-16 21:05:00 +01:00
|
|
|
{
|
2001-10-12 02:07:15 +02:00
|
|
|
Relation rel;
|
2001-10-25 07:50:21 +02:00
|
|
|
Oid myrelid;
|
|
|
|
List *listptr;
|
2000-02-04 19:49:34 +01:00
|
|
|
|
2002-01-04 00:21:32 +01:00
|
|
|
if (!allowSystemTableMods && IsSystemRelationName(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
|
|
|
relationName);
|
2000-07-15 14:37:14 +02:00
|
|
|
#ifndef NO_SECURITY
|
2000-09-06 16:15:31 +02:00
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
2000-07-15 14:37:14 +02:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
2001-11-02 17:30:29 +01:00
|
|
|
/*
|
|
|
|
* Grab an exclusive lock on the target table, which we will NOT
|
|
|
|
* release until end of transaction.
|
|
|
|
*/
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
|
|
|
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
2001-01-07 01:05:22 +01:00
|
|
|
relationName);
|
2000-09-12 23:07:18 +02:00
|
|
|
|
2001-10-12 02:07:15 +02:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
if (inh)
|
|
|
|
{
|
2001-10-12 02:07:15 +02:00
|
|
|
List *child,
|
|
|
|
*children;
|
|
|
|
|
|
|
|
/* this routine is actually in the planner */
|
|
|
|
children = find_all_inheritors(myrelid);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* find_all_inheritors does the recursive search of the
|
2001-10-25 07:50:21 +02:00
|
|
|
* inheritance hierarchy, so all we have to do is process all of
|
|
|
|
* the relids in the list that it returns.
|
2001-10-12 02:07:15 +02:00
|
|
|
*/
|
|
|
|
foreach(child, children)
|
|
|
|
{
|
|
|
|
Oid childrelid = lfirsti(child);
|
|
|
|
char *childrelname;
|
|
|
|
Relation childrel;
|
|
|
|
|
|
|
|
if (childrelid == myrelid)
|
|
|
|
continue;
|
|
|
|
childrel = heap_open(childrelid, AccessExclusiveLock);
|
|
|
|
childrelname = pstrdup(RelationGetRelationName(childrel));
|
|
|
|
heap_close(childrel, AccessExclusiveLock);
|
|
|
|
AlterTableAddConstraint(childrelname, false, newConstraints);
|
|
|
|
pfree(childrelname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach(listptr, newConstraints)
|
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
Node *newConstraint = lfirst(listptr);
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
switch (nodeTag(newConstraint))
|
|
|
|
{
|
|
|
|
case T_Constraint:
|
2000-09-12 23:07:18 +02:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
Constraint *constr = (Constraint *) newConstraint;
|
|
|
|
|
2001-12-04 18:19:48 +01:00
|
|
|
/*
|
|
|
|
* Currently, we only expect to see CONSTR_CHECK nodes
|
|
|
|
* arriving here (see the preprocessing done in
|
|
|
|
* parser/analyze.c). Use a switch anyway to make it
|
|
|
|
* easier to add more code later.
|
|
|
|
*/
|
2001-10-25 07:50:21 +02:00
|
|
|
switch (constr->contype)
|
|
|
|
{
|
|
|
|
case CONSTR_CHECK:
|
2001-03-22 05:01:46 +01:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
ParseState *pstate;
|
|
|
|
bool successful = true;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
ExprContext *econtext;
|
|
|
|
TupleTableSlot *slot;
|
|
|
|
HeapTuple tuple;
|
|
|
|
RangeTblEntry *rte;
|
|
|
|
List *qual;
|
|
|
|
Node *expr;
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
if (constr->name)
|
|
|
|
name = constr->name;
|
|
|
|
else
|
|
|
|
name = "<unnamed>";
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We need to make a parse state and range
|
|
|
|
* table to allow us to transformExpr and
|
|
|
|
* fix_opids to get a version of the
|
|
|
|
* expression we can pass to ExecQual
|
|
|
|
*/
|
|
|
|
pstate = make_parsestate(NULL);
|
|
|
|
rte = addRangeTableEntry(pstate, relationName, NULL,
|
|
|
|
false, true);
|
|
|
|
addRTEtoQuery(pstate, rte, true, true);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert the A_EXPR in raw_expr into an
|
|
|
|
* EXPR
|
|
|
|
*/
|
|
|
|
expr = transformExpr(pstate, constr->raw_expr,
|
|
|
|
EXPR_COLUMN_FIRST);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure it yields a boolean result.
|
|
|
|
*/
|
|
|
|
if (exprType(expr) != BOOLOID)
|
|
|
|
elog(ERROR, "CHECK '%s' does not yield boolean result",
|
|
|
|
name);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure no outside relations are
|
|
|
|
* referred to.
|
|
|
|
*/
|
|
|
|
if (length(pstate->p_rtable) != 1)
|
|
|
|
elog(ERROR, "Only relation '%s' can be referenced in CHECK",
|
|
|
|
relationName);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Might as well try to reduce any
|
|
|
|
* constant expressions.
|
|
|
|
*/
|
|
|
|
expr = eval_const_expressions(expr);
|
|
|
|
|
|
|
|
/* And fix the opids */
|
|
|
|
fix_opids(expr);
|
|
|
|
|
|
|
|
qual = makeList1(expr);
|
|
|
|
|
|
|
|
/* Make tuple slot to hold tuples */
|
|
|
|
slot = MakeTupleTableSlot();
|
|
|
|
ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false);
|
|
|
|
/* Make an expression context for ExecQual */
|
|
|
|
econtext = MakeExprContext(slot, CurrentMemoryContext);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scan through the rows now, checking the
|
|
|
|
* expression at each row.
|
|
|
|
*/
|
|
|
|
scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
2001-03-22 05:01:46 +01:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
|
|
|
|
if (!ExecQual(qual, econtext, true))
|
|
|
|
{
|
|
|
|
successful = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ResetExprContext(econtext);
|
2001-03-22 05:01:46 +01:00
|
|
|
}
|
2000-07-15 14:37:14 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
heap_endscan(scan);
|
2001-10-12 02:07:15 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
FreeExprContext(econtext);
|
|
|
|
pfree(slot);
|
2000-07-15 14:37:14 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
if (!successful)
|
|
|
|
elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
/*
|
|
|
|
* Call AddRelationRawConstraints to do
|
|
|
|
* the real adding -- It duplicates some
|
|
|
|
* of the above, but does not check the
|
|
|
|
* validity of the constraint against
|
|
|
|
* tuples already in the table.
|
|
|
|
*/
|
|
|
|
AddRelationRawConstraints(rel, NIL,
|
2001-10-12 02:07:15 +02:00
|
|
|
makeList1(constr));
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case T_FkConstraint:
|
2001-03-22 05:01:46 +01:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
|
|
|
|
Relation pkrel;
|
|
|
|
HeapScanDesc scan;
|
|
|
|
HeapTuple tuple;
|
|
|
|
Trigger trig;
|
|
|
|
List *list;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
if (is_temp_rel_name(fkconstraint->pktable_name) &&
|
|
|
|
!is_temp_rel_name(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Grab an exclusive lock on the pk table, so that
|
|
|
|
* someone doesn't delete rows out from under us.
|
|
|
|
*/
|
|
|
|
|
|
|
|
pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
|
|
|
|
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "referenced table \"%s\" not a relation",
|
|
|
|
fkconstraint->pktable_name);
|
|
|
|
heap_close(pkrel, NoLock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First we check for limited correctness of the
|
|
|
|
* constraint.
|
|
|
|
*
|
|
|
|
* NOTE: we assume parser has already checked for
|
|
|
|
* existence of an appropriate unique index on the
|
|
|
|
* referenced relation, and that the column datatypes
|
|
|
|
* are comparable.
|
|
|
|
*
|
|
|
|
* Scan through each tuple, calling the RI_FKey_Match_Ins
|
|
|
|
* (insert trigger) as if that tuple had just been
|
|
|
|
* inserted. If any of those fail, it should
|
|
|
|
* elog(ERROR) and that's that.
|
|
|
|
*/
|
|
|
|
|
|
|
|
trig.tgoid = 0;
|
|
|
|
if (fkconstraint->constr_name)
|
|
|
|
trig.tgname = fkconstraint->constr_name;
|
|
|
|
else
|
|
|
|
trig.tgname = "<unknown>";
|
|
|
|
trig.tgfoid = 0;
|
|
|
|
trig.tgtype = 0;
|
|
|
|
trig.tgenabled = TRUE;
|
|
|
|
trig.tgisconstraint = TRUE;
|
|
|
|
trig.tginitdeferred = FALSE;
|
|
|
|
trig.tgdeferrable = FALSE;
|
|
|
|
|
|
|
|
trig.tgargs = (char **) palloc(
|
|
|
|
sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
|
|
|
|
+ length(fkconstraint->pk_attrs)));
|
2000-02-04 19:49:34 +01:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
if (fkconstraint->constr_name)
|
|
|
|
trig.tgargs[0] = fkconstraint->constr_name;
|
|
|
|
else
|
|
|
|
trig.tgargs[0] = "<unknown>";
|
|
|
|
trig.tgargs[1] = (char *) relationName;
|
|
|
|
trig.tgargs[2] = fkconstraint->pktable_name;
|
|
|
|
trig.tgargs[3] = fkconstraint->match_type;
|
|
|
|
count = 4;
|
|
|
|
foreach(list, fkconstraint->fk_attrs)
|
|
|
|
{
|
|
|
|
Ident *fk_at = lfirst(list);
|
|
|
|
|
|
|
|
trig.tgargs[count] = fk_at->name;
|
|
|
|
count += 2;
|
|
|
|
}
|
|
|
|
count = 5;
|
|
|
|
foreach(list, fkconstraint->pk_attrs)
|
|
|
|
{
|
|
|
|
Ident *pk_at = lfirst(list);
|
|
|
|
|
|
|
|
trig.tgargs[count] = pk_at->name;
|
|
|
|
count += 2;
|
|
|
|
}
|
|
|
|
trig.tgnargs = count - 1;
|
|
|
|
|
|
|
|
scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
|
|
|
{
|
|
|
|
/* Make a call to the check function */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* No parameters are passed, but we do set a
|
|
|
|
* context
|
|
|
|
*/
|
|
|
|
FunctionCallInfoData fcinfo;
|
|
|
|
TriggerData trigdata;
|
|
|
|
|
|
|
|
MemSet(&fcinfo, 0, sizeof(fcinfo));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We assume RI_FKey_check_ins won't look at
|
|
|
|
* flinfo...
|
|
|
|
*/
|
|
|
|
|
|
|
|
trigdata.type = T_TriggerData;
|
|
|
|
trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
|
|
|
|
trigdata.tg_relation = rel;
|
|
|
|
trigdata.tg_trigtuple = tuple;
|
|
|
|
trigdata.tg_newtuple = NULL;
|
|
|
|
trigdata.tg_trigger = &trig;
|
|
|
|
|
|
|
|
fcinfo.context = (Node *) &trigdata;
|
|
|
|
|
|
|
|
RI_FKey_check_ins(&fcinfo);
|
|
|
|
}
|
|
|
|
heap_endscan(scan);
|
|
|
|
|
|
|
|
pfree(trig.tgargs);
|
|
|
|
break;
|
2001-03-22 05:01:46 +01:00
|
|
|
}
|
2001-10-25 07:50:21 +02:00
|
|
|
default:
|
|
|
|
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
|
|
|
|
}
|
2001-10-12 02:07:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Close rel, but keep lock till commit */
|
|
|
|
heap_close(rel, NoLock);
|
2000-01-16 21:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2000-01-22 15:20:56 +01:00
|
|
|
/*
|
|
|
|
* ALTER TABLE DROP CONSTRAINT
|
2001-05-30 14:57:36 +02:00
|
|
|
* Note: It is legal to remove a constraint with name "" as it is possible
|
|
|
|
* to add a constraint with name "".
|
|
|
|
* Christopher Kings-Lynne
|
2000-01-22 15:20:56 +01:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
AlterTableDropConstraint(const char *relationName,
|
2000-04-12 19:17:23 +02:00
|
|
|
bool inh, const char *constrName,
|
|
|
|
int behavior)
|
2000-01-16 21:05:00 +01:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
Relation rel;
|
2001-05-30 14:57:36 +02:00
|
|
|
int deleted;
|
|
|
|
|
2002-01-04 00:21:32 +01:00
|
|
|
if (!allowSystemTableMods && IsSystemRelationName(relationName))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
|
|
|
|
relationName);
|
2001-05-30 14:57:36 +02:00
|
|
|
#ifndef NO_SECURITY
|
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
/*
|
|
|
|
* We don't support CASCADE yet - in fact, RESTRICT doesn't work to
|
|
|
|
* the spec either!
|
|
|
|
*/
|
2001-05-30 14:57:36 +02:00
|
|
|
if (behavior == CASCADE)
|
|
|
|
elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword");
|
|
|
|
|
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* Acquire an exclusive lock on the target relation for the duration
|
|
|
|
* of the operation.
|
2001-05-30 14:57:36 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
|
|
|
|
|
|
|
/* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
2001-11-02 17:30:29 +01:00
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
2001-05-30 14:57:36 +02:00
|
|
|
relationName);
|
|
|
|
|
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* Since all we have is the name of the constraint, we have to look
|
|
|
|
* through all catalogs that could possibly contain a constraint for
|
|
|
|
* this relation. We also keep a count of the number of constraints
|
|
|
|
* removed.
|
2001-05-30 14:57:36 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
deleted = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First, we remove all CHECK constraints with the given name
|
|
|
|
*/
|
|
|
|
|
|
|
|
deleted += RemoveCheckConstraint(rel, constrName, inh);
|
|
|
|
|
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY
|
|
|
|
* constraints.
|
2001-05-30 14:57:36 +02:00
|
|
|
*
|
|
|
|
* Unimplemented.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Close the target relation */
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
|
|
|
|
/* If zero constraints deleted, complain */
|
|
|
|
if (deleted == 0)
|
|
|
|
elog(ERROR, "ALTER TABLE / DROP CONSTRAINT: %s does not exist",
|
|
|
|
constrName);
|
|
|
|
/* Otherwise if more than one constraint deleted, notify */
|
|
|
|
else if (deleted > 1)
|
Commit to match discussed elog() changes. Only update is that LOG is
now just below FATAL in server_min_messages. Added more text to
highlight ordering difference between it and client_min_messages.
---------------------------------------------------------------------------
REALLYFATAL => PANIC
STOP => PANIC
New INFO level the prints to client by default
New LOG level the prints to server log by default
Cause VACUUM information to print only to the client
NOTICE => INFO where purely information messages are sent
DEBUG => LOG for purely server status messages
DEBUG removed, kept as backward compatible
DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1 added
DebugLvl removed in favor of new DEBUG[1-5] symbols
New server_min_messages GUC parameter with values:
DEBUG[5-1], INFO, NOTICE, ERROR, LOG, FATAL, PANIC
New client_min_messages GUC parameter with values:
DEBUG[5-1], LOG, INFO, NOTICE, ERROR, FATAL, PANIC
Server startup now logged with LOG instead of DEBUG
Remove debug_level GUC parameter
elog() numbers now start at 10
Add test to print error message if older elog() values are passed to elog()
Bootstrap mode now has a -d that requires an argument, like postmaster
2002-03-02 22:39:36 +01:00
|
|
|
elog(INFO, "Multiple constraints dropped");
|
2000-01-16 21:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-09-12 07:09:57 +02:00
|
|
|
/*
|
|
|
|
* ALTER TABLE OWNER
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
AlterTableOwner(const char *relationName, const char *newOwnerName)
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
Relation class_rel;
|
|
|
|
HeapTuple tuple;
|
2001-01-23 02:48:17 +01:00
|
|
|
int32 newOwnerSysid;
|
2000-09-12 07:09:57 +02:00
|
|
|
Relation idescs[Num_pg_class_indices];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* first check that we are a superuser
|
|
|
|
*/
|
2001-03-22 05:01:46 +01:00
|
|
|
if (!superuser())
|
2000-09-12 07:09:57 +02:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* look up the new owner in pg_shadow and get the sysid
|
|
|
|
*/
|
2001-06-14 03:09:22 +02:00
|
|
|
newOwnerSysid = get_usesysid(newOwnerName);
|
2000-09-12 07:09:57 +02:00
|
|
|
|
|
|
|
/*
|
2000-11-16 23:30:52 +01:00
|
|
|
* find the table's entry in pg_class and make a modifiable copy
|
2000-09-12 07:09:57 +02:00
|
|
|
*/
|
|
|
|
class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
|
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
tuple = SearchSysCacheCopy(RELNAME,
|
|
|
|
PointerGetDatum(relationName),
|
|
|
|
0, 0, 0);
|
2000-09-12 07:09:57 +02:00
|
|
|
if (!HeapTupleIsValid(tuple))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
|
|
|
|
relationName);
|
|
|
|
|
2001-01-07 01:05:22 +01:00
|
|
|
switch (((Form_pg_class) GETSTRUCT(tuple))->relkind)
|
|
|
|
{
|
|
|
|
case RELKIND_RELATION:
|
|
|
|
case RELKIND_INDEX:
|
|
|
|
case RELKIND_VIEW:
|
|
|
|
case RELKIND_SEQUENCE:
|
|
|
|
/* ok to change owner */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table, index, view, or sequence",
|
|
|
|
relationName);
|
|
|
|
}
|
2000-09-12 07:09:57 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* modify the table's entry and write to the heap
|
|
|
|
*/
|
|
|
|
((Form_pg_class) GETSTRUCT(tuple))->relowner = newOwnerSysid;
|
|
|
|
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(class_rel, &tuple->t_self, tuple);
|
2000-09-12 07:09:57 +02:00
|
|
|
|
|
|
|
/* Keep the catalog indices up to date */
|
|
|
|
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
|
|
|
|
CatalogIndexInsert(idescs, Num_pg_class_indices, class_rel, tuple);
|
|
|
|
CatalogCloseIndices(Num_pg_class_indices, idescs);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* unlock everything and return
|
|
|
|
*/
|
|
|
|
heap_freetuple(tuple);
|
2001-06-12 07:55:50 +02:00
|
|
|
heap_close(class_rel, NoLock);
|
2000-09-12 07:09:57 +02:00
|
|
|
}
|
|
|
|
|
2000-09-12 23:07:18 +02:00
|
|
|
|
2000-07-04 01:10:14 +02:00
|
|
|
/*
|
|
|
|
* ALTER TABLE CREATE TOAST TABLE
|
|
|
|
*/
|
|
|
|
void
|
2000-07-05 14:45:31 +02:00
|
|
|
AlterTableCreateToastTable(const char *relationName, bool silent)
|
2000-07-04 01:10:14 +02:00
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
Relation rel;
|
|
|
|
Oid myrelid;
|
|
|
|
HeapTuple reltup;
|
|
|
|
HeapTupleData classtuple;
|
|
|
|
TupleDesc tupdesc;
|
|
|
|
Relation class_rel;
|
|
|
|
Buffer buffer;
|
|
|
|
Relation ridescs[Num_pg_class_indices];
|
|
|
|
Oid toast_relid;
|
|
|
|
Oid toast_idxid;
|
|
|
|
char toast_relname[NAMEDATALEN + 1];
|
|
|
|
char toast_idxname[NAMEDATALEN + 1];
|
|
|
|
IndexInfo *indexInfo;
|
2001-08-10 20:57:42 +02:00
|
|
|
Oid classObjectId[2];
|
2000-07-04 01:10:14 +02:00
|
|
|
|
|
|
|
/*
|
2000-07-04 08:11:54 +02:00
|
|
|
* permissions checking. XXX exactly what is appropriate here?
|
2000-07-04 01:10:14 +02:00
|
|
|
*/
|
|
|
|
#ifndef NO_SECURITY
|
2000-09-06 16:15:31 +02:00
|
|
|
if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
|
2000-07-04 01:10:14 +02:00
|
|
|
elog(ERROR, "ALTER TABLE: permission denied");
|
|
|
|
#endif
|
|
|
|
|
2000-07-05 14:45:31 +02:00
|
|
|
/*
|
2000-08-25 20:05:54 +02:00
|
|
|
* Grab an exclusive lock on the target table, which we will NOT
|
|
|
|
* release until end of transaction.
|
|
|
|
*/
|
|
|
|
rel = heap_openr(relationName, AccessExclusiveLock);
|
2001-01-07 01:05:22 +01:00
|
|
|
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
|
|
|
|
relationName);
|
|
|
|
|
2000-08-25 20:05:54 +02:00
|
|
|
myrelid = RelationGetRelid(rel);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lock the pg_class tuple for update (is that really needed?)
|
2000-07-05 14:45:31 +02:00
|
|
|
*/
|
2000-08-25 20:05:54 +02:00
|
|
|
class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
|
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
reltup = SearchSysCache(RELNAME,
|
|
|
|
PointerGetDatum(relationName),
|
|
|
|
0, 0, 0);
|
2000-07-05 14:45:31 +02:00
|
|
|
if (!HeapTupleIsValid(reltup))
|
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
|
|
|
|
relationName);
|
|
|
|
classtuple.t_self = reltup->t_self;
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(reltup);
|
|
|
|
|
2000-07-05 14:45:31 +02:00
|
|
|
switch (heap_mark4update(class_rel, &classtuple, &buffer))
|
|
|
|
{
|
|
|
|
case HeapTupleSelfUpdated:
|
|
|
|
case HeapTupleMayBeUpdated:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "couldn't lock pg_class tuple");
|
|
|
|
}
|
|
|
|
reltup = heap_copytuple(&classtuple);
|
|
|
|
ReleaseBuffer(buffer);
|
|
|
|
|
2000-07-04 01:10:14 +02:00
|
|
|
/*
|
2001-01-07 01:05:22 +01:00
|
|
|
* Is it already toasted?
|
2000-07-04 01:10:14 +02:00
|
|
|
*/
|
2000-08-25 20:05:54 +02:00
|
|
|
if (((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid != InvalidOid)
|
2000-07-05 14:45:31 +02:00
|
|
|
{
|
2000-10-16 19:08:11 +02:00
|
|
|
if (silent)
|
2000-07-05 14:45:31 +02:00
|
|
|
{
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
heap_close(class_rel, NoLock);
|
2000-07-05 15:22:25 +02:00
|
|
|
heap_freetuple(reltup);
|
2000-07-05 14:45:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2000-08-25 20:05:54 +02:00
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table",
|
|
|
|
relationName);
|
2000-10-16 19:08:11 +02:00
|
|
|
}
|
2000-07-04 01:10:14 +02:00
|
|
|
|
|
|
|
/*
|
2000-08-25 20:05:54 +02:00
|
|
|
* Check to see whether the table actually needs a TOAST table.
|
2000-07-04 01:10:14 +02:00
|
|
|
*/
|
2001-03-22 05:01:46 +01:00
|
|
|
if (!needs_toast_table(rel))
|
2000-07-05 15:22:25 +02:00
|
|
|
{
|
2000-10-16 19:08:11 +02:00
|
|
|
if (silent)
|
2000-07-05 15:22:25 +02:00
|
|
|
{
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
heap_close(class_rel, NoLock);
|
|
|
|
heap_freetuple(reltup);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2000-08-25 20:05:54 +02:00
|
|
|
elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table",
|
|
|
|
relationName);
|
|
|
|
}
|
2000-07-04 01:10:14 +02:00
|
|
|
|
|
|
|
/*
|
2000-07-04 08:11:54 +02:00
|
|
|
* Create the toast table and its index
|
2000-07-04 01:10:14 +02:00
|
|
|
*/
|
2000-07-04 08:11:54 +02:00
|
|
|
sprintf(toast_relname, "pg_toast_%u", myrelid);
|
|
|
|
sprintf(toast_idxname, "pg_toast_%u_idx", myrelid);
|
|
|
|
|
|
|
|
/* this is pretty painful... need a tuple descriptor */
|
|
|
|
tupdesc = CreateTemplateTupleDesc(3);
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1,
|
|
|
|
"chunk_id",
|
|
|
|
OIDOID,
|
|
|
|
-1, 0, false);
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2,
|
|
|
|
"chunk_seq",
|
|
|
|
INT4OID,
|
|
|
|
-1, 0, false);
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3,
|
|
|
|
"chunk_data",
|
2000-07-05 18:17:43 +02:00
|
|
|
BYTEAOID,
|
2000-07-04 08:11:54 +02:00
|
|
|
-1, 0, false);
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2000-08-04 06:16:17 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* Ensure that the toast table doesn't itself get toasted, or we'll be
|
|
|
|
* toast :-(. This is essential for chunk_data because type bytea is
|
|
|
|
* toastable; hit the other two just to be sure.
|
2000-08-04 06:16:17 +02:00
|
|
|
*/
|
|
|
|
tupdesc->attrs[0]->attstorage = 'p';
|
|
|
|
tupdesc->attrs[1]->attstorage = 'p';
|
|
|
|
tupdesc->attrs[2]->attstorage = 'p';
|
2000-07-04 08:11:54 +02:00
|
|
|
|
2000-07-06 01:12:09 +02:00
|
|
|
/*
|
|
|
|
* Note: the toast relation is considered a "normal" relation even if
|
|
|
|
* its master relation is a temp table. There cannot be any naming
|
|
|
|
* collision, and the toast rel will be destroyed when its master is,
|
|
|
|
* so there's no need to handle the toast rel as temp.
|
|
|
|
*/
|
2000-08-25 20:05:54 +02:00
|
|
|
toast_relid = heap_create_with_catalog(toast_relname, tupdesc,
|
2001-08-10 20:57:42 +02:00
|
|
|
RELKIND_TOASTVALUE, false,
|
2000-08-25 20:05:54 +02:00
|
|
|
false, true);
|
2000-07-04 08:11:54 +02:00
|
|
|
|
|
|
|
/* make the toast relation visible, else index creation will fail */
|
|
|
|
CommandCounterIncrement();
|
|
|
|
|
2001-08-10 20:57:42 +02:00
|
|
|
/*
|
|
|
|
* Create unique index on chunk_id, chunk_seq.
|
|
|
|
*
|
|
|
|
* NOTE: the tuple toaster could actually function with a single-column
|
2001-10-25 07:50:21 +02:00
|
|
|
* index on chunk_id only. However, it couldn't be unique then. We
|
|
|
|
* want it to be unique as a check against the possibility of
|
|
|
|
* duplicate TOAST chunk OIDs. Too, the index might be a little more
|
|
|
|
* efficient this way, since btree isn't all that happy with large
|
|
|
|
* numbers of equal keys.
|
2001-08-10 20:57:42 +02:00
|
|
|
*/
|
2000-07-15 00:18:02 +02:00
|
|
|
|
|
|
|
indexInfo = makeNode(IndexInfo);
|
2001-08-10 20:57:42 +02:00
|
|
|
indexInfo->ii_NumIndexAttrs = 2;
|
|
|
|
indexInfo->ii_NumKeyAttrs = 2;
|
2000-07-15 00:18:02 +02:00
|
|
|
indexInfo->ii_KeyAttrNumbers[0] = 1;
|
2001-08-10 20:57:42 +02:00
|
|
|
indexInfo->ii_KeyAttrNumbers[1] = 2;
|
2001-07-16 07:07:00 +02:00
|
|
|
indexInfo->ii_Predicate = NIL;
|
2000-07-15 00:18:02 +02:00
|
|
|
indexInfo->ii_FuncOid = InvalidOid;
|
2001-08-10 20:57:42 +02:00
|
|
|
indexInfo->ii_Unique = true;
|
2000-07-15 00:18:02 +02:00
|
|
|
|
2001-08-21 18:36:06 +02:00
|
|
|
classObjectId[0] = OID_BTREE_OPS_OID;
|
|
|
|
classObjectId[1] = INT4_BTREE_OPS_OID;
|
2000-07-15 00:18:02 +02:00
|
|
|
|
2001-08-10 20:57:42 +02:00
|
|
|
toast_idxid = index_create(toast_relname, toast_idxname, indexInfo,
|
|
|
|
BTREE_AM_OID, classObjectId,
|
2001-08-21 18:36:06 +02:00
|
|
|
true, true);
|
2000-07-04 08:11:54 +02:00
|
|
|
|
2000-08-25 20:05:54 +02:00
|
|
|
/*
|
2001-10-25 07:50:21 +02:00
|
|
|
* Update toast rel's pg_class entry to show that it has an index. The
|
|
|
|
* index OID is stored into the reltoastidxid field for easy access by
|
|
|
|
* the tuple toaster.
|
2000-08-25 20:05:54 +02:00
|
|
|
*/
|
2001-08-10 20:57:42 +02:00
|
|
|
setRelhasindex(toast_relid, true, true, toast_idxid);
|
2000-07-04 01:10:14 +02:00
|
|
|
|
|
|
|
/*
|
2001-08-10 20:57:42 +02:00
|
|
|
* Store the toast table's OID in the parent relation's tuple
|
2000-07-04 01:10:14 +02:00
|
|
|
*/
|
|
|
|
((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
|
2001-01-23 05:32:23 +01:00
|
|
|
simple_heap_update(class_rel, &reltup->t_self, reltup);
|
2000-07-04 01:10:14 +02:00
|
|
|
|
2000-07-05 14:45:31 +02:00
|
|
|
/*
|
|
|
|
* Keep catalog indices current
|
|
|
|
*/
|
2000-07-04 01:10:14 +02:00
|
|
|
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
|
2000-07-05 14:45:31 +02:00
|
|
|
CatalogIndexInsert(ridescs, Num_pg_class_indices, class_rel, reltup);
|
2000-07-04 01:10:14 +02:00
|
|
|
CatalogCloseIndices(Num_pg_class_indices, ridescs);
|
|
|
|
|
|
|
|
heap_freetuple(reltup);
|
|
|
|
|
2000-07-05 18:17:43 +02:00
|
|
|
/*
|
2000-07-06 01:12:09 +02:00
|
|
|
* Close relations and make changes visible
|
2000-07-05 18:17:43 +02:00
|
|
|
*/
|
2000-07-05 14:45:31 +02:00
|
|
|
heap_close(class_rel, NoLock);
|
2000-07-04 01:10:14 +02:00
|
|
|
heap_close(rel, NoLock);
|
2000-07-05 15:22:25 +02:00
|
|
|
|
|
|
|
CommandCounterIncrement();
|
2000-07-04 01:10:14 +02:00
|
|
|
}
|
|
|
|
|
2000-08-25 20:05:54 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* Check to see whether the table needs a TOAST table. It does only if
|
2000-08-25 20:05:54 +02:00
|
|
|
* (1) there are any toastable attributes, and (2) the maximum length
|
|
|
|
* of a tuple could exceed TOAST_TUPLE_THRESHOLD. (We don't want to
|
|
|
|
* create a toast table for something like "f1 varchar(20)".)
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
needs_toast_table(Relation rel)
|
|
|
|
{
|
|
|
|
int32 data_length = 0;
|
|
|
|
bool maxlength_unknown = false;
|
|
|
|
bool has_toastable_attrs = false;
|
|
|
|
TupleDesc tupdesc;
|
2001-03-22 05:01:46 +01:00
|
|
|
Form_pg_attribute *att;
|
2000-08-25 20:05:54 +02:00
|
|
|
int32 tuple_length;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
tupdesc = rel->rd_att;
|
|
|
|
att = tupdesc->attrs;
|
|
|
|
|
|
|
|
for (i = 0; i < tupdesc->natts; i++)
|
|
|
|
{
|
|
|
|
data_length = att_align(data_length, att[i]->attlen, att[i]->attalign);
|
|
|
|
if (att[i]->attlen >= 0)
|
|
|
|
{
|
|
|
|
/* Fixed-length types are never toastable */
|
|
|
|
data_length += att[i]->attlen;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
int32 maxlen = type_maximum_size(att[i]->atttypid,
|
|
|
|
att[i]->atttypmod);
|
2000-08-25 20:05:54 +02:00
|
|
|
|
|
|
|
if (maxlen < 0)
|
|
|
|
maxlength_unknown = true;
|
|
|
|
else
|
|
|
|
data_length += maxlen;
|
|
|
|
if (att[i]->attstorage != 'p')
|
|
|
|
has_toastable_attrs = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!has_toastable_attrs)
|
|
|
|
return false; /* nothing to toast? */
|
|
|
|
if (maxlength_unknown)
|
|
|
|
return true; /* any unlimited-length attrs? */
|
|
|
|
tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) +
|
|
|
|
BITMAPLEN(tupdesc->natts)) +
|
|
|
|
MAXALIGN(data_length);
|
|
|
|
return (tuple_length > TOAST_TUPLE_THRESHOLD);
|
|
|
|
}
|
2001-10-25 07:50:21 +02:00
|
|
|
|
2000-01-16 21:05:00 +01:00
|
|
|
/*
|
|
|
|
* LOCK TABLE
|
|
|
|
*/
|
1998-12-18 10:10:39 +01:00
|
|
|
void
|
1999-05-26 00:43:53 +02:00
|
|
|
LockTableCommand(LockStmt *lockstmt)
|
1998-12-18 10:10:39 +01:00
|
|
|
{
|
2001-10-25 07:50:21 +02:00
|
|
|
List *p;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Iterate over the list and open, lock, and close the relations one
|
|
|
|
* at a time
|
2001-08-10 16:30:15 +02:00
|
|
|
*/
|
2001-08-04 21:39:00 +02:00
|
|
|
|
2001-10-25 07:50:21 +02:00
|
|
|
foreach(p, lockstmt->rellist)
|
|
|
|
{
|
|
|
|
char *relname = strVal(lfirst(p));
|
|
|
|
int aclresult;
|
2001-11-02 17:30:29 +01:00
|
|
|
Relation rel;
|
2001-10-25 07:50:21 +02:00
|
|
|
|
|
|
|
if (lockstmt->mode == AccessShareLock)
|
|
|
|
aclresult = pg_aclcheck(relname, GetUserId(),
|
|
|
|
ACL_SELECT);
|
|
|
|
else
|
|
|
|
aclresult = pg_aclcheck(relname, GetUserId(),
|
|
|
|
ACL_UPDATE | ACL_DELETE);
|
|
|
|
|
|
|
|
if (aclresult != ACLCHECK_OK)
|
|
|
|
elog(ERROR, "LOCK TABLE: permission denied");
|
|
|
|
|
2001-11-02 17:30:29 +01:00
|
|
|
rel = relation_openr(relname, lockstmt->mode);
|
2000-09-12 06:33:18 +02:00
|
|
|
|
2001-11-02 17:30:29 +01:00
|
|
|
/* Currently, we only allow plain tables to be locked */
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
|
|
elog(ERROR, "LOCK TABLE: %s is not a table",
|
|
|
|
relname);
|
2000-09-12 06:33:18 +02:00
|
|
|
|
2001-11-02 17:30:29 +01:00
|
|
|
relation_close(rel, NoLock); /* close rel, keep lock */
|
|
|
|
}
|
2000-09-12 06:33:18 +02:00
|
|
|
}
|