Here is my much-promised patch to let people add UNIQUE constraints after
table creation time. Big deal you say - but this patch is the basis of the next thing which is adding PRIMARY KEYs after table creation time. (Which is currently impossible without twiddling catalogs) Rundown ------- * I have made the makeObjectName function of analyze.c non-static, and exported it in analyze.h * I have included analyze.h and defrem.h into command.c, to support makingObjectNames and creating indices * I removed the 'case CONSTR_PRIMARY' clause so that it properly fails and says you can't add primary keys, rather than just doing nothing and reporting nothing!!! * I have modified the docs. Algorithm --------- * If name specified is null, search for a new valid constraint name. I'm not sure if I should "lock" my generated name somehow tho - should I open the relation before doing this step? * Open relation in access exclusive mode * Check that the constraint does not already exist * Define the new index * Warn if they're doubling up on an existing index Christopher Kings-Lynne
This commit is contained in:
parent
68e190cfa4
commit
bd9b32803b
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.26 2001/09/03 12:57:49 petere Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.27 2001/09/07 21:57:53 momjian Exp $
|
||||
Postgres documentation
|
||||
-->
|
||||
|
||||
@ -211,9 +211,9 @@ ALTER TABLE <replaceable class="PARAMETER">table</replaceable>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In the current implementation, only FOREIGN KEY and CHECK constraints can
|
||||
be added to a table. To create a unique constraint, create
|
||||
a unique index (see <xref linkend="SQL-CREATEINDEX"
|
||||
In the current implementation, only UNIQUE, FOREIGN KEY and CHECK constraints can
|
||||
be added to a table. To create a primary constraint, create
|
||||
a unique, not null index (see <xref linkend="SQL-CREATEINDEX"
|
||||
endterm="SQL-CREATEINDEX-title">).
|
||||
</para>
|
||||
|
||||
@ -297,6 +297,13 @@ ALTER TABLE distributors DROP CONSTRAINT zipchk
|
||||
ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses(address) MATCH FULL
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To add a (multi-column) unique constraint to a table:
|
||||
<programlisting>
|
||||
ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zipcode)
|
||||
</programlisting>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="R1-SQL-ALTERTABLE-3">
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.141 2001/08/21 16:36:01 tgl Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.142 2001/09/07 21:57:53 momjian Exp $
|
||||
*
|
||||
* NOTES
|
||||
* The PerformAddAttribute() code, like most of the relation
|
||||
@ -32,6 +32,7 @@
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/command.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "commands/defrem.h" /* For add constraint unique, primary */
|
||||
#include "executor/execdefs.h"
|
||||
#include "executor/executor.h"
|
||||
#include "miscadmin.h"
|
||||
@ -42,6 +43,7 @@
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_oper.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/analyze.h" /* For add constraint unique, primary */
|
||||
#include "utils/acl.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
@ -1339,11 +1341,160 @@ AlterTableAddConstraint(char *relationName,
|
||||
|
||||
break;
|
||||
}
|
||||
case CONSTR_PRIMARY:
|
||||
case CONSTR_UNIQUE:
|
||||
{
|
||||
char *iname = constr->name;
|
||||
bool istemp = is_temp_rel_name(relationName);
|
||||
Relation rel;
|
||||
List *indexoidlist;
|
||||
List *indexoidscan;
|
||||
Form_pg_attribute *rel_attrs;
|
||||
int num_keys = 0;
|
||||
int keys_matched = 0;
|
||||
bool index_found = false;
|
||||
bool index_found_unique = false;
|
||||
bool index_found_primary = false;
|
||||
|
||||
break;
|
||||
}
|
||||
/* If the constraint name is not specified, generate a name */
|
||||
if (iname == NULL) {
|
||||
Oid indoid;
|
||||
int pass = 0;
|
||||
char *typename = palloc(NAMEDATALEN);
|
||||
Ident *key;
|
||||
|
||||
/* Assume that the length of the attr list is already > 0 */
|
||||
|
||||
/* Get the first attribute so we can use its name */
|
||||
key = (Ident *)lfirst(constr->keys);
|
||||
|
||||
/* Initialise typename to 'key' */
|
||||
snprintf(typename, NAMEDATALEN, "key");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
iname = makeObjectName(relationName, key->name, typename);
|
||||
|
||||
/* Check for a conflict */
|
||||
indoid = RelnameFindRelid(iname);
|
||||
|
||||
/* If the oid was not found, then we have a safe name */
|
||||
if ((!istemp && !OidIsValid(indoid)) ||
|
||||
(istemp && !is_temp_rel_name(iname)))
|
||||
break;
|
||||
|
||||
/* Found a conflict, so try a new name component */
|
||||
pfree(iname);
|
||||
snprintf(typename, NAMEDATALEN, "key%d", ++pass);
|
||||
}
|
||||
}
|
||||
|
||||
/* Need to check for unique key already on field(s) */
|
||||
rel = heap_openr(relationName, AccessExclusiveLock);
|
||||
|
||||
/*
|
||||
* First we check for limited correctness of the
|
||||
* constraint
|
||||
*/
|
||||
|
||||
rel_attrs = rel->rd_att->attrs;
|
||||
|
||||
/* Retrieve the oids of all indices on the relation */
|
||||
indexoidlist = RelationGetIndexList(rel);
|
||||
index_found = false;
|
||||
index_found_unique = false;
|
||||
index_found_primary = false;
|
||||
|
||||
/* Loop over all indices on the relation */
|
||||
foreach(indexoidscan, indexoidlist)
|
||||
{
|
||||
Oid indexoid = lfirsti(indexoidscan);
|
||||
HeapTuple indexTuple;
|
||||
Form_pg_index indexStruct;
|
||||
List *keyl;
|
||||
int i;
|
||||
|
||||
indexTuple = SearchSysCache(INDEXRELID,
|
||||
ObjectIdGetDatum(indexoid),
|
||||
0, 0, 0);
|
||||
|
||||
if (!HeapTupleIsValid(indexTuple))
|
||||
elog(ERROR, "ALTER TABLE/ADD CONSTRAINT: Index \"%u\" not found",
|
||||
indexoid);
|
||||
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
|
||||
|
||||
/*
|
||||
* Make sure this index has the same number of
|
||||
* keys as the constraint -- It obviously won't match otherwise.
|
||||
*/
|
||||
for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++);
|
||||
num_keys = length(constr->keys);
|
||||
keys_matched = 0;
|
||||
|
||||
if (i == num_keys)
|
||||
{
|
||||
/* Loop over each key in the constraint and check that there is a
|
||||
corresponding key in the index. */
|
||||
i = 0;
|
||||
foreach(keyl, constr->keys)
|
||||
{
|
||||
Ident *key = lfirst(keyl);
|
||||
|
||||
/* Look at key[i] in the index and check that it is over the same column
|
||||
as key[i] in the constraint. This is to differentiate between (a,b)
|
||||
and (b,a) */
|
||||
if (i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0)
|
||||
{
|
||||
int keyno = indexStruct->indkey[i];
|
||||
|
||||
if (keyno > 0)
|
||||
{
|
||||
char *name = NameStr(rel_attrs[keyno - 1]->attname);
|
||||
if (strcmp(name, key->name) == 0) keys_matched++;
|
||||
}
|
||||
}
|
||||
else elog(ERROR, "ALTER TABLE/ADD CONSTRAINT: Key \"%u[%u]\" not found", indexoid, i);
|
||||
i++;
|
||||
}
|
||||
if (keys_matched == num_keys) {
|
||||
index_found = true;
|
||||
index_found_unique = indexStruct->indisunique;
|
||||
index_found_primary = indexStruct->indisprimary;
|
||||
if (index_found_unique || index_found_primary) break;
|
||||
}
|
||||
}
|
||||
ReleaseSysCache(indexTuple);
|
||||
}
|
||||
|
||||
freeList(indexoidlist);
|
||||
|
||||
if (index_found_primary)
|
||||
elog(ERROR, "Unique primary key already defined on relation \"%s\"", relationName);
|
||||
else if (index_found_unique)
|
||||
elog(ERROR, "Unique constraint already defined on the specified attributes in relation \"%s\"", relationName);
|
||||
|
||||
/* If everything is ok, create the new index (constraint) */
|
||||
DefineIndex(
|
||||
relationName,
|
||||
iname,
|
||||
"btree",
|
||||
constr->keys,
|
||||
true,
|
||||
false,
|
||||
NULL,
|
||||
NIL);
|
||||
|
||||
/* Issue notice */
|
||||
elog(NOTICE, "ALTER TABLE/ADD UNIQUE will create implicit index '%s' for table '%s'",
|
||||
iname, relationName);
|
||||
if (index_found)
|
||||
elog(NOTICE, "Unique constraint supercedes existing index on relation \"%s\". Drop the existing index to remove redundancy.", relationName);
|
||||
pfree(iname);
|
||||
|
||||
/* Finally, close relation */
|
||||
heap_close(rel, NoLock);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.197 2001/08/24 20:03:45 petere Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.198 2001/09/07 21:57:53 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -562,7 +562,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
|
||||
* from the truncated characters. Currently it seems best to keep it simple,
|
||||
* so that the generated names are easily predictable by a person.
|
||||
*/
|
||||
static char *
|
||||
char *
|
||||
makeObjectName(char *name1, char *name2, char *typename)
|
||||
{
|
||||
char *name;
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $Id: analyze.h,v 1.14 2001/01/24 19:43:26 momjian Exp $
|
||||
* $Id: analyze.h,v 1.15 2001/09/07 21:57:53 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -19,4 +19,7 @@ extern List *parse_analyze(Node *parseTree, ParseState *parentParseState);
|
||||
|
||||
extern void CheckSelectForUpdate(Query *qry);
|
||||
|
||||
/* This was exported to allow ADD CONSTRAINT to make use of it */
|
||||
extern char *makeObjectName(char *name1, char *name2, char *typename);
|
||||
|
||||
#endif /* ANALYZE_H */
|
||||
|
Loading…
x
Reference in New Issue
Block a user