From 739a0566a639bc0e052447f09fafdb8f5bb37d9b Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Sat, 15 Jul 2000 12:37:14 +0000
Subject: [PATCH] This should be a slighly more complete patch for
 commands/command.c AlterTableAddConstraint.  The major changes from the last
 patch are that it should hopefully check for references to temp tables (not
 in the shadow case, but at defination time) from permanent tables in foreign
 keys and refuse them and that it doesn't allow the table(s) being constrained
 to be views (because those cases don't currently work).

Stephan SzaboThis should be a slighly more complete patch for commands/command.c
AlterTableAddConstraint.  The major changes from the last patch
are that it should hopefully check for references to temp tables
(not in the shadow case, but at defination time) from permanent tables in
foreign keys and refuse them and that it doesn't allow the table(s)
being constrained to be views (because those cases don't currently
work).

Stephan Szabo
---
 src/backend/commands/command.c | 192 ++++++++++++++++++++++++++++++++-
 1 file changed, 188 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c
index be24c696a7c..bcd0b4b2fb7 100644
--- a/src/backend/commands/command.c
+++ b/src/backend/commands/command.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.89 2000/07/14 22:17:42 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.90 2000/07/15 12:37:14 momjian Exp $
  *
  * NOTES
  *	  The PerformAddAttribute() code, like most of the relation
@@ -33,6 +33,18 @@
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "commands/trigger.h"
+
+#include "parser/parse_expr.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/planmain.h"
+#include "optimizer/clauses.h"
+#include "rewrite/rewriteSupport.h"
+#include "commands/view.h"
+#include "utils/temprel.h"
+#include "executor/spi_priv.h"
+
 #ifdef	_DROP_COLUMN_HACK__
 #include "catalog/pg_index.h"
 #include "parser/parse.h"
@@ -1067,13 +1079,158 @@ void
 AlterTableAddConstraint(const char *relationName,
 						bool inh, Node *newConstraint)
 {
+	char rulequery[41+NAMEDATALEN]; 
+	void *qplan;
+	char nulls[1]="";
+
 	if (newConstraint == NULL)
 		elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint.");
 
+#ifndef NO_SECURITY
+	if (!pg_ownercheck(UserName, relationName, RELNAME))
+		elog(ERROR, "ALTER TABLE: permission denied");
+#endif
+
+	/* check to see if the table to be constrained is a view. */
+	sprintf(rulequery, "select * from pg_views where viewname='%s'", relationName);
+	if (SPI_connect()!=SPI_OK_CONNECT)
+		elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view - SPI_connect failure..", relationName);
+        qplan=SPI_prepare(rulequery, 0, NULL);
+	if (!qplan)
+		elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view - SPI_prepare failure.", relationName);
+	qplan=SPI_saveplan(qplan);
+	if (SPI_execp(qplan, NULL, nulls, 1)!=SPI_OK_SELECT) 
+		elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view - SPI_execp failure.", relationName);
+        if (SPI_processed != 0)
+                elog(ERROR, "ALTER TABLE: Cannot add constraints to views.");
+        if (SPI_finish() != SPI_OK_FINISH)
+                elog(NOTICE, "SPI_finish() failed in ALTER TABLE");
+		
 	switch (nodeTag(newConstraint))
 	{
 		case T_Constraint:
-			elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented");
+			{
+				Constraint *constr=(Constraint *)newConstraint;
+				switch (constr->contype) {
+					case CONSTR_CHECK:
+					{
+						ParseState *pstate;
+						bool successful=TRUE;
+						HeapScanDesc scan;
+					        ExprContext *econtext;
+					        TupleTableSlot *slot = makeNode(TupleTableSlot);
+						HeapTuple tuple;
+					        RangeTblEntry *rte = makeNode(RangeTblEntry);
+					        List       *rtlist;
+					        List       *qual;
+						List       *constlist;
+						Relation	rel;
+						Node *expr;
+						char *name;
+						if (constr->name)
+							name=constr->name;
+						else
+							name="<unnamed>";
+
+						rel = heap_openr(relationName, AccessExclusiveLock);
+
+						/*
+						 * Scan all of the rows, looking for a false match
+						 */
+						scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+						AssertState(scan != NULL);
+
+						/* 
+						 *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);
+					        makeRangeTable(pstate, NULL);
+					        addRangeTableEntry(pstate, relationName, 
+							makeAttr(relationName, NULL), false, true,true);
+						constlist=lcons(constr, NIL);
+
+						/* 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 = lcons(expr, NIL);
+       						rte->relname = relationName;
+					        rte->ref = makeNode(Attr);
+					        rte->ref->relname = rte->relname;
+					        rte->relid = RelationGetRelid(rel);
+					        rtlist = lcons(rte, NIL);
+
+						/* 
+						 * Scan through the rows now, making the necessary things for
+						 * ExecQual, and then call it to evaluate the expression.
+						 */
+						while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+						{
+						        slot->val = tuple;
+						        slot->ttc_shouldFree = false;
+						        slot->ttc_descIsNew = true;
+						        slot->ttc_tupleDescriptor = rel->rd_att;
+						        slot->ttc_buffer = InvalidBuffer;
+						        slot->ttc_whichplan = -1;
+
+							econtext = MakeExprContext(slot, CurrentMemoryContext);
+						        econtext->ecxt_range_table = rtlist;            /* range table */
+						        if (!ExecQual(qual, econtext, true)) {
+								successful=false;
+								break;
+						        }
+							FreeExprContext(econtext);
+						}
+
+					        pfree(slot);
+					        pfree(rtlist);
+					        pfree(rte);
+
+						heap_endscan(scan);
+						heap_close(rel, NoLock);		
+
+						if (!successful) 
+						{
+							elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
+						}
+						/* 
+						 * 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, constlist);
+					        pfree(constlist);
+
+						break;
+					}
+					default:
+						elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+				}
+			}
+			break;
 		case T_FkConstraint:
 			{
 				FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
@@ -1084,6 +1241,26 @@ AlterTableAddConstraint(const char *relationName,
 				List	   *list;
 				int			count;
 
+				if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
+				    get_temp_rel_by_username(relationName)==NULL) {
+					elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+				}
+
+				/* check to see if the referenced table is a view. */
+				sprintf(rulequery, "select * from pg_views where viewname='%s'", fkconstraint->pktable_name);
+				if (SPI_connect()!=SPI_OK_CONNECT)
+					elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view.", relationName);
+			        qplan=SPI_prepare(rulequery, 0, NULL);
+				if (!qplan)
+					elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view.", relationName);
+				qplan=SPI_saveplan(qplan);
+				if (SPI_execp(qplan, NULL, nulls, 1)!=SPI_OK_SELECT) 
+					elog(ERROR, "ALTER TABLE: Unable to determine if %s is a view.", relationName);
+			        if (SPI_processed != 0)
+			                elog(ERROR, "ALTER TABLE: Cannot add constraints to views.");
+			        if (SPI_finish() != SPI_OK_FINISH)
+			                elog(NOTICE, "SPI_finish() failed in RI_FKey_check()");
+
 				/*
 				 * Grab an exclusive lock on the pk table, so that someone
 				 * doesn't delete rows out from under us.
@@ -1101,7 +1278,10 @@ AlterTableAddConstraint(const char *relationName,
 				 */
 				rel = heap_openr(relationName, AccessExclusiveLock);
 				trig.tgoid = 0;
-				trig.tgname = "<unknown>";
+				if (fkconstraint->constr_name)
+					trig.tgname = fkconstraint->constr_name;
+				else
+					trig.tgname = "<unknown>";
 				trig.tgfoid = 0;
 				trig.tgtype = 0;
 				trig.tgenabled = TRUE;
@@ -1113,7 +1293,10 @@ AlterTableAddConstraint(const char *relationName,
 					 sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
 									   + length(fkconstraint->pk_attrs)));
 
-				trig.tgargs[0] = "<unnamed>";
+				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;
@@ -1446,3 +1629,4 @@ LockTableCommand(LockStmt *lockstmt)
 
 	heap_close(rel, NoLock);	/* close rel, keep lock */
 }
+
-- 
GitLab