Commit bd9b3280 authored by Bruce Momjian's avatar Bruce Momjian

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
parent 68e190cf
<!-- <!--
$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 Postgres documentation
--> -->
...@@ -211,9 +211,9 @@ ALTER TABLE <replaceable class="PARAMETER">table</replaceable> ...@@ -211,9 +211,9 @@ ALTER TABLE <replaceable class="PARAMETER">table</replaceable>
</para> </para>
<para> <para>
In the current implementation, only FOREIGN KEY and CHECK constraints can In the current implementation, only UNIQUE, FOREIGN KEY and CHECK constraints can
be added to a table. To create a unique constraint, create be added to a table. To create a primary constraint, create
a unique index (see <xref linkend="SQL-CREATEINDEX" a unique, not null index (see <xref linkend="SQL-CREATEINDEX"
endterm="SQL-CREATEINDEX-title">). endterm="SQL-CREATEINDEX-title">).
</para> </para>
...@@ -297,6 +297,13 @@ ALTER TABLE distributors DROP CONSTRAINT zipchk ...@@ -297,6 +297,13 @@ ALTER TABLE distributors DROP CONSTRAINT zipchk
ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses(address) MATCH FULL ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses(address) MATCH FULL
</programlisting> </programlisting>
</para> </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>
<refsect1 id="R1-SQL-ALTERTABLE-3"> <refsect1 id="R1-SQL-ALTERTABLE-3">
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* The PerformAddAttribute() code, like most of the relation * The PerformAddAttribute() code, like most of the relation
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/command.h" #include "commands/command.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "commands/defrem.h" /* For add constraint unique, primary */
#include "executor/execdefs.h" #include "executor/execdefs.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "miscadmin.h" #include "miscadmin.h"
...@@ -42,6 +43,7 @@ ...@@ -42,6 +43,7 @@
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parse_oper.h" #include "parser/parse_oper.h"
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "parser/analyze.h" /* For add constraint unique, primary */
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
...@@ -1339,11 +1341,160 @@ AlterTableAddConstraint(char *relationName, ...@@ -1339,11 +1341,160 @@ AlterTableAddConstraint(char *relationName,
break; break;
} }
case CONSTR_PRIMARY: case CONSTR_UNIQUE:
{ {
char *iname = constr->name;
break; 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;
/* 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: default:
elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type."); elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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) ...@@ -562,7 +562,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* from the truncated characters. Currently it seems best to keep it simple, * from the truncated characters. Currently it seems best to keep it simple,
* so that the generated names are easily predictable by a person. * so that the generated names are easily predictable by a person.
*/ */
static char * char *
makeObjectName(char *name1, char *name2, char *typename) makeObjectName(char *name1, char *name2, char *typename)
{ {
char *name; char *name;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -19,4 +19,7 @@ extern List *parse_analyze(Node *parseTree, ParseState *parentParseState);
extern void CheckSelectForUpdate(Query *qry); 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 */ #endif /* ANALYZE_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment