001: package com.quadcap.sql;
002:
003: /* Copyright 1999 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.ByteArrayOutputStream;
042: import java.io.Externalizable;
043: import java.io.IOException;
044: import java.io.ObjectInput;
045: import java.io.ObjectOutput;
046:
047: import java.util.Vector;
048:
049: import java.sql.ResultSet;
050: import java.sql.SQLException;
051:
052: import com.quadcap.sql.io.ObjectInputStream;
053: import com.quadcap.sql.io.ObjectOutputStream;
054:
055: import com.quadcap.sql.file.BlockFile;
056:
057: import com.quadcap.sql.index.Btree;
058:
059: import com.quadcap.sql.types.Op;
060: import com.quadcap.sql.types.Value;
061:
062: import com.quadcap.util.Debug;
063: import com.quadcap.util.Util;
064:
065: /**
066: * Constraint for imported keys (i.e., this table references another
067: * table)
068: *
069: * @author Stan Bailes
070: */
071: public class ImportedKeyConstraint extends ForeignKeyConstraint
072: implements Externalizable {
073: long indexRoot = -1;
074: transient Btree index = null;
075: transient Btree fIndex = null;
076: transient ExportedKeyConstraint ec = null;
077:
078: /**
079: * Default constructor required for serialization
080: */
081: public ImportedKeyConstraint() {
082: }
083:
084: /**
085: * Explicit constructor parser.
086: */
087: public ImportedKeyConstraint(String name, String fTableName) {
088: super (name, fTableName);
089: }
090:
091: /**
092: * Explicit constructor with named columns
093: */
094: public ImportedKeyConstraint(String name, Vector colNames,
095: String fTableName, Vector fColNames) {
096: super (name, colNames, fTableName, fColNames);
097: }
098:
099: /**
100: * For <b>ALTER TABLE ADD CONSTRAINT</b>, we need to build our
101: * foreign index.
102: */
103: public void add(Session session) throws SQLException, IOException {
104: Database db = session.getDatabase();
105: BlockFile file = db.getFile();
106: getFCols(db);
107: if (fConstraint == null) {
108: throw new SQLException("No suitable foreign key", "23000");
109: }
110:
111: indexRoot = file.newPage();
112: index = new Btree(file, indexRoot, true);
113:
114: String ecName = getExportConstraintName();
115: ec = new ExportedKeyConstraint(ecName, fColNames, table
116: .getName(), colNames, this , fConstraint);
117: ec.setForeignKeyCols(getColumns());
118: session.doStep(new AddConstraint(session, fTable, ec, false));
119:
120: if (!table.isUnderConstruction()) {
121: IndexCursor c = table.getCursor(session, null);
122: if (c != null) {
123: try {
124: while (c.next()) {
125: Row row = c.getRow();
126: long rowId = c.getRowId();
127: checkInsert(session, row);
128: applyInsert(session, row, rowId, null);
129: }
130: } finally {
131: c.close();
132: }
133: }
134: }
135: db.updateRelation(fTable);
136: }
137:
138: /**
139: * On <b>ALTER TABLE DROP CONSTRAINT</b>, delete the index and free
140: * the file resources it holds.
141: */
142: public void delete(Session session) throws SQLException,
143: IOException {
144: // Don't bother if there isn't an index....
145: if (indexRoot > 0) {
146: Database db = session.getDatabase();
147: getIndex(db);
148: if (index != null) {
149: index.free();
150: index = null;
151: }
152: indexRoot = -1;
153: }
154: }
155:
156: /**
157: * Return a name for the auto-generated 'export' constraint.
158: */
159: final String getExportConstraintName() {
160: StringBuffer sb = new StringBuffer("__ec_");
161: sb.append(table.getName());
162: sb.append('_');
163: sb.append(name);
164: return sb.toString();
165: }
166:
167: /**
168: * On insert, verify that the referenced keys exist in the foreign
169: * table.
170: */
171: public void checkInsert(Session session, Row row)
172: throws SQLException, IOException {
173: Database db = session.getDatabase();
174: int[] k = getColumns();
175: int[] fk = getFCols(db);
176:
177: Row r = new Row(fTable.getColumnCount());
178: boolean anyNull = false;
179: boolean allNull = true;
180: for (int i = 0; i < k.length; i++) {
181: int col = k[i];
182: int fcol = fk[i];
183: Value v = row.item(col);
184: boolean isNull = Value.isNull(v);
185: anyNull |= isNull;
186: allNull &= isNull;
187: r.set(fcol, v);
188: }
189: UniqueConstraint con = fTable.getIndexForColumns(fk);
190: byte[] key = con.makeKey(session, r, 0);
191: Btree fTree = con.getIndex(db);
192: if (fTree.get(key) == null
193: && !allNull
194: && (!anyNull || ((spec & (Constraint.FULL | Constraint.PARTIAL)) != 0))) {
195: if (isSelfReferencing(db) && checkSelfReferencing(row)) {
196: // it's ok, after all.
197: } else {
198: throw new SQLException(
199: "Foreign Key Constraint Violation: no parent: "
200: + this .toString(), "23000");
201: }
202: }
203: }
204:
205: /**
206: * On insert, add the new 'export' constraint value
207: */
208: public void applyInsert(Session session, Row row, long rowId,
209: Constraint activeIndex) throws SQLException, IOException {
210: byte[] key = makeKey(session, row, rowId);
211: AddIndexEntry add = new AddIndexEntry(session, this , key, rowId);
212: if (activeIndex == this ) {
213: session.addPendingAction(add);
214: } else {
215: session.doStep(add);
216: }
217: }
218:
219: /**
220: * On delete, remove the constraints holding the corresponding
221: * row values.
222: */
223: public void applyDelete(Session session, Row row, long rowId,
224: Constraint activeIndex) throws SQLException, IOException {
225: Database db = session.getDatabase();
226: byte[] key = makeKey(session, row, rowId);
227: if (index == null)
228: getIndex(db);
229: DeleteIndexEntry del = new DeleteIndexEntry(session, this , key);
230: if (activeIndex == this ) {
231: session.addPendingAction(del);
232: } else {
233: session.doStep(del);
234: }
235: }
236:
237: /**
238: * On update, if the imported key changes, we need to modify the
239: * index. Once this happens, we create a session context in which
240: * to record the index changes so we can do them all together, at
241: * the end.
242: */
243: public void checkUpdate(Session session, byte[] oldKey, Row row,
244: Row oldRow, long rowId, Constraint activeIndex)
245: throws SQLException, IOException {
246: checkInsert(session, row);
247: getComparator();
248: byte[] key = makeKey(session, row, rowId);
249: if (activeIndex != this )
250: oldKey = makeKey(session, oldRow, rowId);
251: if (compare.compare(key, oldKey) != 0) {
252: UpdateIndex ui = (UpdateIndex) session.getContext(this ,
253: isDeferred());
254: if (ui == null) {
255: ui = new UpdateIndex(session, this );
256: session.putContext(this , isDeferred(), ui);
257: }
258: ui.addEntry(key, oldKey, rowId);
259: }
260: }
261:
262: /**
263: * Check for the possibility that a row satisfies its own constraints.
264: * It's a strange world we live in, get used to it.
265: */
266: boolean checkSelfReferencing(Row row) throws SQLException {
267: boolean anyNull = false;
268: boolean allNull = true;
269: boolean allMatch = true;
270: for (int i = 0; i < fCols.length; i++) {
271: Value v = row.item(fCols[i]);
272: Value r = row.item(columns[i]);
273: boolean isNull = Value.isNull(v);
274: anyNull |= isNull;
275: allNull &= isNull;
276: if (allMatch)
277: allMatch = Value.boolOp(Op.EQ, v, r);
278: }
279: if (allMatch || allNull)
280: return true;
281: if (anyNull
282: && (spec & (Constraint.FULL | Constraint.PARTIAL)) != 0)
283: return true;
284: return false;
285: }
286:
287: /**
288: * Get (create if necessary) the Btree for the foreign index.
289: */
290: final Btree getForeignIndex(Session session) throws SQLException,
291: IOException {
292: if (fIndex == null) {
293: fIndex = fConstraint.getIndex(session.getDatabase());
294: }
295: return fIndex;
296: }
297:
298: /**
299: * Get the export constraint if it exists already.
300: */
301: public ExportedKeyConstraint findExportedKeyConstraint(Database db)
302: throws IOException, SQLException {
303: if (ec == null) {
304: Table t = getFTable(db);
305: String ecName = getExportConstraintName();
306: ec = (ExportedKeyConstraint) t.getConstraint(ecName);
307: }
308: return ec;
309: }
310:
311: /**
312: * For the purposes of creating a unique key, the rowId is included,
313: * whereas for purposes of checking foreign key integrity, the rowId
314: * is ignored, only the key value matters.
315: */
316: byte[] makeKey(Session session, Row row, long rowId)
317: throws SQLException {
318: return Key.makeKey(table, row, getColumns(), rowId, true); // !!
319: }
320:
321: /**
322: * Get the index (create it if necessary). The key for this index includes
323: * the actual key fields plus the row id to ensure uniqueness for Btreee
324: * operations. Some operations require that we use a special comparator
325: * which ignores the rowId part.
326: */
327: public Btree getIndex(Database db) throws IOException {
328: if (index == null) {
329: index = new Btree(db.getFile(), indexRoot, false);
330: }
331: return index;
332: }
333:
334: /**
335: * Externalizable: read me from a stream
336: */
337: public void readExternal(ObjectInput in) throws IOException,
338: ClassNotFoundException {
339: super .readExternal(in);
340: indexRoot = in.readLong();
341: }
342:
343: /**
344: * Externalizable: write me to a stream
345: */
346: public void writeExternal(ObjectOutput out) throws IOException {
347: super.writeExternal(out);
348: out.writeLong(indexRoot);
349: }
350:
351: }
|