--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.ConstraintConstantAction\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.execute;\r
+\r
+import org.apache.derby.catalog.UUID;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.PreparedStatement;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.ForeignKeyConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ReferencedKeyConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.GroupFetchScanController;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.NumberDataValue;\r
+/**\r
+ * This class describes actions that are ALWAYS performed for a\r
+ * constraint creation at Execution time.\r
+ *\r
+ * @version 0.1\r
+ */\r
+\r
+public abstract class ConstraintConstantAction extends DDLSingleTableConstantAction \r
+{\r
+\r
+ protected String constraintName;\r
+ protected int constraintType;\r
+ protected String tableName;\r
+ protected String schemaName;\r
+ protected UUID schemaId;\r
+ protected IndexConstantAction indexAction;\r
+\r
+ // CONSTRUCTORS\r
+ /**\r
+ * Make one of these puppies.\r
+ *\r
+ * @param constraintName Constraint name.\r
+ * @param constraintType Constraint type.\r
+ * @param tableName Table name.\r
+ * @param tableId UUID of table.\r
+ * @param schemaName schema that table and constraint lives in.\r
+ * @param indexAction IndexConstantAction for constraint (if necessary)\r
+ * RESOLVE - the next parameter should go away once we use UUIDs\r
+ * (Generated constraint names will be based off of uuids)\r
+ */\r
+ ConstraintConstantAction(\r
+ String constraintName,\r
+ int constraintType,\r
+ String tableName,\r
+ UUID tableId,\r
+ String schemaName,\r
+ IndexConstantAction indexAction)\r
+ {\r
+ super(tableId);\r
+ this.constraintName = constraintName;\r
+ this.constraintType = constraintType;\r
+ this.tableName = tableName;\r
+ this.indexAction = indexAction;\r
+ this.schemaName = schemaName;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(schemaName != null, "Constraint schema name is null");\r
+ }\r
+ }\r
+\r
+ // Class implementation\r
+\r
+ /**\r
+ * Get the constraint type.\r
+ *\r
+ * @return The constraint type\r
+ */\r
+ public int getConstraintType()\r
+ {\r
+ return constraintType;\r
+ }\r
+\r
+ /**\r
+ * Get the constraint name\r
+ *\r
+ * @return the constraint name\r
+ */\r
+ public String getConstraintName() { return constraintName; }\r
+\r
+ /**\r
+ * Get the associated index constant action.\r
+ *\r
+ * @return the constant action for the backing index\r
+ */\r
+ public IndexConstantAction getIndexAction() { return indexAction; }\r
+\r
+ /**\r
+ * Make sure that the foreign key constraint is valid\r
+ * with the existing data in the target table. Open\r
+ * the table, if there aren't any rows, ok. If there\r
+ * are rows, open a scan on the referenced key with\r
+ * table locking at level 2. Pass in the scans to\r
+ * the BulkRIChecker. If any rows fail, barf.\r
+ *\r
+ * @param tc transaction controller\r
+ * @param dd data dictionary\r
+ * @param fk foreign key constraint\r
+ * @param refcd referenced key\r
+ * @param indexTemplateRow index template row\r
+ *\r
+ * @exception StandardException on error\r
+ */\r
+ static void validateFKConstraint\r
+ (\r
+ TransactionController tc,\r
+ DataDictionary dd,\r
+ ForeignKeyConstraintDescriptor fk,\r
+ ReferencedKeyConstraintDescriptor refcd,\r
+ ExecRow indexTemplateRow \r
+ )\r
+ throws StandardException\r
+ {\r
+\r
+ GroupFetchScanController refScan = null;\r
+\r
+ GroupFetchScanController fkScan = \r
+ tc.openGroupFetchScan(\r
+ fk.getIndexConglomerateDescriptor(dd).getConglomerateNumber(),\r
+ false, // hold \r
+ 0, // read only\r
+ tc.MODE_TABLE, // already locked\r
+ tc.ISOLATION_READ_COMMITTED, // whatever\r
+ (FormatableBitSet)null, // retrieve all fields\r
+ (DataValueDescriptor[])null, // startKeyValue\r
+ ScanController.GE, // startSearchOp\r
+ null, // qualifier\r
+ (DataValueDescriptor[])null, // stopKeyValue\r
+ ScanController.GT // stopSearchOp \r
+ );\r
+\r
+ try\r
+ {\r
+ /*\r
+ ** If we have no rows, then we are ok. This will \r
+ ** catch the CREATE TABLE T (x int references P) case\r
+ ** (as well as an ALTER TABLE ADD CONSTRAINT where there\r
+ ** are no rows in the target table).\r
+ */ \r
+ if (!fkScan.next())\r
+ {\r
+ fkScan.close();\r
+ return;\r
+ }\r
+\r
+ fkScan.reopenScan(\r
+ (DataValueDescriptor[])null, // startKeyValue\r
+ ScanController.GE, // startSearchOp\r
+ null, // qualifier\r
+ (DataValueDescriptor[])null, // stopKeyValue\r
+ ScanController.GT // stopSearchOp \r
+ );\r
+\r
+ /*\r
+ ** Make sure each row in the new fk has a matching\r
+ ** referenced key. No need to get any special locking\r
+ ** on the referenced table because it cannot delete\r
+ ** any keys we match because it will block on the table\r
+ ** lock on the fk table (we have an ex tab lock on\r
+ ** the target table of this ALTER TABLE command).\r
+ ** Note that we are doing row locking on the referenced\r
+ ** table. We could speed things up and get table locking\r
+ ** because we are likely to be hitting a lot of rows\r
+ ** in the referenced table, but we are going to err\r
+ ** on the side of concurrency here.\r
+ */\r
+ refScan = \r
+ tc.openGroupFetchScan(\r
+ refcd.getIndexConglomerateDescriptor(dd).getConglomerateNumber(),\r
+ false, // hold \r
+ 0, // read only\r
+ tc.MODE_RECORD,\r
+ tc.ISOLATION_READ_COMMITTED, // read committed is good enough\r
+ (FormatableBitSet)null, // retrieve all fields\r
+ (DataValueDescriptor[])null, // startKeyValue\r
+ ScanController.GE, // startSearchOp\r
+ null, // qualifier\r
+ (DataValueDescriptor[])null, // stopKeyValue\r
+ ScanController.GT // stopSearchOp \r
+ );\r
+\r
+ RIBulkChecker riChecker = new RIBulkChecker(refScan, \r
+ fkScan, \r
+ indexTemplateRow, \r
+ true, // fail on 1st failure\r
+ (ConglomerateController)null,\r
+ (ExecRow)null);\r
+\r
+ int numFailures = riChecker.doCheck();\r
+ if (numFailures > 0)\r
+ {\r
+ StandardException se = StandardException.newException(SQLState.LANG_ADD_FK_CONSTRAINT_VIOLATION, \r
+ fk.getConstraintName(), \r
+ fk.getTableDescriptor().getName());\r
+ throw se;\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ if (fkScan != null)\r
+ {\r
+ fkScan.close();\r
+ fkScan = null;\r
+ }\r
+ if (refScan != null)\r
+ {\r
+ refScan.close();\r
+ refScan = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Evaluate a check constraint or not null column constraint. \r
+ * Generate a query of the\r
+ * form SELECT COUNT(*) FROM t where NOT(<check constraint>)\r
+ * and run it by compiling and executing it. Will\r
+ * work ok if the table is empty and query returns null.\r
+ *\r
+ * @param constraintName constraint name\r
+ * @param constraintText constraint text\r
+ * @param td referenced table\r
+ * @param lcc the language connection context\r
+ * @param isCheckConstraint the constraint is a check constraint\r
+ *\r
+ * @return true if null constraint passes, false otherwise\r
+ *\r
+ * @exception StandardException if check constraint fails\r
+ */\r
+ static boolean validateConstraint\r
+ (\r
+ String constraintName,\r
+ String constraintText,\r
+ TableDescriptor td,\r
+ LanguageConnectionContext lcc,\r
+ boolean isCheckConstraint\r
+ )\r
+ throws StandardException\r
+ {\r
+ StringBuffer checkStmt = new StringBuffer();\r
+ /* should not use select sum(not(<check-predicate>) ? 1: 0) because\r
+ * that would generate much more complicated code and may exceed Java\r
+ * limits if we have a large number of check constraints, beetle 4347\r
+ */\r
+ checkStmt.append("SELECT COUNT(*) FROM ");\r
+ checkStmt.append(td.getQualifiedName());\r
+ checkStmt.append(" WHERE NOT(");\r
+ checkStmt.append(constraintText);\r
+ checkStmt.append(")");\r
+ \r
+ ResultSet rs = null;\r
+ try\r
+ {\r
+ PreparedStatement ps = lcc.prepareInternalStatement(checkStmt.toString());\r
+\r
+ // This is a substatement; for now, we do not set any timeout\r
+ // for it. We might change this behaviour later, by linking\r
+ // timeout to its parent statement's timeout settings.\r
+ rs = ps.execute(lcc, false, 0L);\r
+ ExecRow row = rs.getNextRow();\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (row == null)\r
+ {\r
+ SanityManager.THROWASSERT("did not get any rows back from query: "+checkStmt.toString());\r
+ }\r
+ }\r
+\r
+ DataValueDescriptor[] rowArray = row.getRowArray();\r
+ Number value = ((Number)((NumberDataValue)row.getRowArray()[0]).getObject());\r
+ /*\r
+ ** Value may be null if there are no rows in the\r
+ ** table.\r
+ */\r
+ if ((value != null) && (value.longValue() != 0))\r
+ { \r
+ //check constraint violated\r
+ if (isCheckConstraint)\r
+ throw StandardException.newException(SQLState.LANG_ADD_CHECK_CONSTRAINT_FAILED, \r
+ constraintName, td.getQualifiedName(), value.toString());\r
+ /*\r
+ * for not null constraint violations exception will be thrown in caller\r
+ * check constraint will not get here since exception is thrown\r
+ * above\r
+ */\r
+ return false;\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ if (rs != null)\r
+ {\r
+ rs.close();\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+}\r