001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.ConstraintConstantAction
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.execute;
023:
024: import org.apache.derby.iapi.services.sanity.SanityManager;
025:
026: import org.apache.derby.catalog.UUID;
027: import org.apache.derby.iapi.services.uuid.UUIDFactory;
028:
029: import org.apache.derby.iapi.error.StandardException;
030:
031: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
032:
033: import org.apache.derby.iapi.sql.dictionary.CheckConstraintDescriptor;
034: import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
035: import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;
036: import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
037: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
038: import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
039: import org.apache.derby.iapi.sql.dictionary.ForeignKeyConstraintDescriptor;
040: import org.apache.derby.iapi.sql.dictionary.ReferencedKeyConstraintDescriptor;
041: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
042: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
043:
044: import org.apache.derby.iapi.types.DataValueFactory;
045:
046: import org.apache.derby.iapi.reference.SQLState;
047:
048: import org.apache.derby.iapi.sql.execute.ConstantAction;
049: import org.apache.derby.iapi.sql.execute.ExecRow;
050:
051: import org.apache.derby.iapi.sql.ResultSet;
052: import org.apache.derby.iapi.sql.Statement;
053: import org.apache.derby.iapi.sql.PreparedStatement;
054:
055: import org.apache.derby.iapi.types.NumberDataValue;
056:
057: import org.apache.derby.iapi.store.access.ConglomerateController;
058: import org.apache.derby.iapi.store.access.GroupFetchScanController;
059: import org.apache.derby.iapi.store.access.ScanController;
060: import org.apache.derby.iapi.store.access.TransactionController;
061:
062: import org.apache.derby.iapi.types.DataValueDescriptor;
063:
064: import org.apache.derby.catalog.UUID;
065:
066: import org.apache.derby.iapi.services.io.FormatableBitSet;
067:
068: /**
069: * This class describes actions that are ALWAYS performed for a
070: * constraint creation at Execution time.
071: *
072: * @version 0.1
073: * @author Jerry Brenner
074: */
075:
076: public abstract class ConstraintConstantAction extends
077: DDLSingleTableConstantAction {
078:
079: protected String constraintName;
080: protected int constraintType;
081: protected String tableName;
082: protected String schemaName;
083: protected UUID schemaId;
084: protected IndexConstantAction indexAction;
085:
086: // CONSTRUCTORS
087: /**
088: * Make one of these puppies.
089: *
090: * @param constraintName Constraint name.
091: * @param constraintType Constraint type.
092: * @param tableName Table name.
093: * @param tableId UUID of table.
094: * @param schemaName schema that table and constraint lives in.
095: * @param indexAction IndexConstantAction for constraint (if necessary)
096: * RESOLVE - the next parameter should go away once we use UUIDs
097: * (Generated constraint names will be based off of uuids)
098: */
099: ConstraintConstantAction(String constraintName, int constraintType,
100: String tableName, UUID tableId, String schemaName,
101: IndexConstantAction indexAction) {
102: super (tableId);
103: this .constraintName = constraintName;
104: this .constraintType = constraintType;
105: this .tableName = tableName;
106: this .indexAction = indexAction;
107: this .schemaName = schemaName;
108:
109: if (SanityManager.DEBUG) {
110: SanityManager.ASSERT(schemaName != null,
111: "Constraint schema name is null");
112: }
113: }
114:
115: // Class implementation
116:
117: /**
118: * Get the constraint type.
119: *
120: * @return The constraint type
121: */
122: public int getConstraintType() {
123: return constraintType;
124: }
125:
126: /**
127: * Get the constraint name
128: *
129: * @return the constraint name
130: */
131: public String getConstraintName() {
132: return constraintName;
133: }
134:
135: /**
136: * Get the associated index constant action.
137: *
138: * @return the constant action for the backing index
139: */
140: public IndexConstantAction getIndexAction() {
141: return indexAction;
142: }
143:
144: /**
145: * Make sure that the foreign key constraint is valid
146: * with the existing data in the target table. Open
147: * the table, if there aren't any rows, ok. If there
148: * are rows, open a scan on the referenced key with
149: * table locking at level 2. Pass in the scans to
150: * the BulkRIChecker. If any rows fail, barf.
151: *
152: * @param tc transaction controller
153: * @param dd data dictionary
154: * @param fk foreign key constraint
155: * @param refcd referenced key
156: * @param indexTemplateRow index template row
157: *
158: * @exception StandardException on error
159: */
160: static void validateFKConstraint(TransactionController tc,
161: DataDictionary dd, ForeignKeyConstraintDescriptor fk,
162: ReferencedKeyConstraintDescriptor refcd,
163: ExecRow indexTemplateRow) throws StandardException {
164:
165: GroupFetchScanController refScan = null;
166:
167: GroupFetchScanController fkScan = tc.openGroupFetchScan(fk
168: .getIndexConglomerateDescriptor(dd)
169: .getConglomerateNumber(), false, // hold
170: 0, // read only
171: tc.MODE_TABLE, // already locked
172: tc.ISOLATION_READ_COMMITTED, // whatever
173: (FormatableBitSet) null, // retrieve all fields
174: (DataValueDescriptor[]) null, // startKeyValue
175: ScanController.GE, // startSearchOp
176: null, // qualifier
177: (DataValueDescriptor[]) null, // stopKeyValue
178: ScanController.GT // stopSearchOp
179: );
180:
181: try {
182: /*
183: ** If we have no rows, then we are ok. This will
184: ** catch the CREATE TABLE T (x int references P) case
185: ** (as well as an ALTER TABLE ADD CONSTRAINT where there
186: ** are no rows in the target table).
187: */
188: if (!fkScan.next()) {
189: fkScan.close();
190: return;
191: }
192:
193: fkScan.reopenScan((DataValueDescriptor[]) null, // startKeyValue
194: ScanController.GE, // startSearchOp
195: null, // qualifier
196: (DataValueDescriptor[]) null, // stopKeyValue
197: ScanController.GT // stopSearchOp
198: );
199:
200: /*
201: ** Make sure each row in the new fk has a matching
202: ** referenced key. No need to get any special locking
203: ** on the referenced table because it cannot delete
204: ** any keys we match because it will block on the table
205: ** lock on the fk table (we have an ex tab lock on
206: ** the target table of this ALTER TABLE command).
207: ** Note that we are doing row locking on the referenced
208: ** table. We could speed things up and get table locking
209: ** because we are likely to be hitting a lot of rows
210: ** in the referenced table, but we are going to err
211: ** on the side of concurrency here.
212: */
213: refScan = tc.openGroupFetchScan(refcd
214: .getIndexConglomerateDescriptor(dd)
215: .getConglomerateNumber(), false, // hold
216: 0, // read only
217: tc.MODE_RECORD, tc.ISOLATION_READ_COMMITTED, // read committed is good enough
218: (FormatableBitSet) null, // retrieve all fields
219: (DataValueDescriptor[]) null, // startKeyValue
220: ScanController.GE, // startSearchOp
221: null, // qualifier
222: (DataValueDescriptor[]) null, // stopKeyValue
223: ScanController.GT // stopSearchOp
224: );
225:
226: RIBulkChecker riChecker = new RIBulkChecker(refScan,
227: fkScan, indexTemplateRow, true, // fail on 1st failure
228: (ConglomerateController) null, (ExecRow) null);
229:
230: int numFailures = riChecker.doCheck();
231: if (numFailures > 0) {
232: StandardException se = StandardException.newException(
233: SQLState.LANG_ADD_FK_CONSTRAINT_VIOLATION, fk
234: .getConstraintName(), fk
235: .getTableDescriptor().getName());
236: throw se;
237: }
238: } finally {
239: if (fkScan != null) {
240: fkScan.close();
241: fkScan = null;
242: }
243: if (refScan != null) {
244: refScan.close();
245: refScan = null;
246: }
247: }
248: }
249:
250: /**
251: * Evaluate a check constraint or not null column constraint.
252: * Generate a query of the
253: * form SELECT COUNT(*) FROM t where NOT(<check constraint>)
254: * and run it by compiling and executing it. Will
255: * work ok if the table is empty and query returns null.
256: *
257: * @param constraintName constraint name
258: * @param constraintText constraint text
259: * @param td referenced table
260: * @param lcc the language connection context
261: * @param isCheckConstraint the constraint is a check constraint
262: *
263: * @return true if null constraint passes, false otherwise
264: *
265: * @exception StandardException if check constraint fails
266: */
267: static boolean validateConstraint(String constraintName,
268: String constraintText, TableDescriptor td,
269: LanguageConnectionContext lcc, boolean isCheckConstraint)
270: throws StandardException {
271: StringBuffer checkStmt = new StringBuffer();
272: /* should not use select sum(not(<check-predicate>) ? 1: 0) because
273: * that would generate much more complicated code and may exceed Java
274: * limits if we have a large number of check constraints, beetle 4347
275: */
276: checkStmt.append("SELECT COUNT(*) FROM ");
277: checkStmt.append(td.getQualifiedName());
278: checkStmt.append(" WHERE NOT(");
279: checkStmt.append(constraintText);
280: checkStmt.append(")");
281:
282: ResultSet rs = null;
283: try {
284: PreparedStatement ps = lcc
285: .prepareInternalStatement(checkStmt.toString());
286:
287: // This is a substatement; for now, we do not set any timeout
288: // for it. We might change this behaviour later, by linking
289: // timeout to its parent statement's timeout settings.
290: rs = ps.execute(lcc, false, 0L);
291: ExecRow row = rs.getNextRow();
292: if (SanityManager.DEBUG) {
293: if (row == null) {
294: SanityManager
295: .THROWASSERT("did not get any rows back from query: "
296: + checkStmt.toString());
297: }
298: }
299:
300: DataValueDescriptor[] rowArray = row.getRowArray();
301: Number value = ((Number) ((NumberDataValue) row
302: .getRowArray()[0]).getObject());
303: /*
304: ** Value may be null if there are no rows in the
305: ** table.
306: */
307: if ((value != null) && (value.longValue() != 0)) {
308: //check constraint violated
309: if (isCheckConstraint)
310: throw StandardException.newException(
311: SQLState.LANG_ADD_CHECK_CONSTRAINT_FAILED,
312: constraintName, td.getQualifiedName(),
313: value.toString());
314: /*
315: * for not null constraint violations exception will be thrown in caller
316: * check constraint will not get here since exception is thrown
317: * above
318: */
319: return false;
320: }
321: } finally {
322: if (rs != null) {
323: rs.close();
324: }
325: }
326: return true;
327: }
328: }
|