Quadcap Embeddable Database

com/quadcap/sql/ImportedKeyConstraint.java

Go to the documentation of this file.
00001 package com.quadcap.sql; 00002 00003 /* Copyright 1999 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.ByteArrayOutputStream; 00042 import java.io.Externalizable; 00043 import java.io.IOException; 00044 import java.io.ObjectInput; 00045 import java.io.ObjectOutput; 00046 00047 import java.util.Vector; 00048 00049 import java.sql.ResultSet; 00050 import java.sql.SQLException; 00051 00052 import com.quadcap.sql.io.ObjectInputStream; 00053 import com.quadcap.sql.io.ObjectOutputStream; 00054 00055 import com.quadcap.sql.file.BlockFile; 00056 00057 import com.quadcap.sql.index.Btree; 00058 00059 import com.quadcap.sql.types.Op; 00060 import com.quadcap.sql.types.Value; 00061 00062 import com.quadcap.util.Debug; 00063 import com.quadcap.util.Util; 00064 00065 /** 00066 * Constraint for imported keys (i.e., this table references another 00067 * table) 00068 * 00069 * @author Stan Bailes 00070 */ 00071 public class ImportedKeyConstraint extends ForeignKeyConstraint 00072 implements Externalizable 00073 { 00074 long indexRoot = -1; 00075 transient Btree index = null; 00076 transient Btree fIndex = null; 00077 transient ExportedKeyConstraint ec = null; 00078 00079 /** 00080 * Default constructor required for serialization 00081 */ 00082 public ImportedKeyConstraint() {} 00083 00084 /** 00085 * Explicit constructor parser. 00086 */ 00087 public ImportedKeyConstraint(String name, String fTableName) { 00088 super(name, fTableName); 00089 } 00090 00091 /** 00092 * Explicit constructor with named columns 00093 */ 00094 public ImportedKeyConstraint(String name, Vector colNames, 00095 String fTableName, Vector fColNames) { 00096 super(name, colNames, fTableName, fColNames); 00097 } 00098 00099 /** 00100 * For <b>ALTER TABLE ADD CONSTRAINT</b>, we need to build our 00101 * foreign index. 00102 */ 00103 public void add(Session session) throws SQLException, IOException { 00104 Database db = session.getDatabase(); 00105 BlockFile file = db.getFile(); 00106 getFCols(db); 00107 if (fConstraint == null) { 00108 throw new SQLException("No suitable foreign key", "23000"); 00109 } 00110 00111 indexRoot = file.newPage(); 00112 index = new Btree(file, indexRoot, true); 00113 00114 String ecName = getExportConstraintName(); 00115 ec = new ExportedKeyConstraint(ecName, fColNames, table.getName(), 00116 colNames, this, fConstraint); 00117 ec.setForeignKeyCols(getColumns()); 00118 session.doStep(new AddConstraint(session, fTable, ec, false)); 00119 00120 if (!table.isUnderConstruction()) { 00121 IndexCursor c = table.getCursor(session, null); 00122 if (c != null) { 00123 try { 00124 while (c.next()) { 00125 Row row = c.getRow(); 00126 long rowId = c.getRowId(); 00127 checkInsert(session, row); 00128 applyInsert(session, row, rowId, null); 00129 } 00130 } finally { 00131 c.close(); 00132 } 00133 } 00134 } 00135 db.updateRelation(fTable); 00136 } 00137 00138 /** 00139 * On <b>ALTER TABLE DROP CONSTRAINT</b>, delete the index and free 00140 * the file resources it holds. 00141 */ 00142 public void delete(Session session) throws SQLException, IOException { 00143 // Don't bother if there isn't an index.... 00144 if (indexRoot > 0) { 00145 Database db = session.getDatabase(); 00146 getIndex(db); 00147 if (index != null) { 00148 index.free(); 00149 index = null; 00150 } 00151 indexRoot = -1; 00152 } 00153 } 00154 00155 /** 00156 * Return a name for the auto-generated 'export' constraint. 00157 */ 00158 final String getExportConstraintName() { 00159 StringBuffer sb = new StringBuffer("__ec_"); 00160 sb.append(table.getName()); 00161 sb.append('_'); 00162 sb.append(name); 00163 return sb.toString(); 00164 } 00165 00166 /** 00167 * On insert, verify that the referenced keys exist in the foreign 00168 * table. 00169 */ 00170 public void checkInsert(Session session, Row row) 00171 throws SQLException, IOException 00172 { 00173 Database db = session.getDatabase(); 00174 int[] k = getColumns(); 00175 int[] fk = getFCols(db); 00176 00177 Row r = new Row(fTable.getColumnCount()); 00178 boolean anyNull = false; 00179 boolean allNull = true; 00180 for (int i = 0; i < k.length; i++) { 00181 int col = k[i]; 00182 int fcol = fk[i]; 00183 Value v = row.item(col); 00184 boolean isNull = Value.isNull(v); 00185 anyNull |= isNull; 00186 allNull &= isNull; 00187 r.set(fcol, v); 00188 } 00189 UniqueConstraint con = fTable.getIndexForColumns(fk); 00190 byte[] key = con.makeKey(session, r, 0); 00191 Btree fTree = con.getIndex(db); 00192 if (fTree.get(key) == null && !allNull 00193 && (!anyNull || ((spec & (Constraint.FULL | 00194 Constraint.PARTIAL)) != 0))) { 00195 if (isSelfReferencing(db) && checkSelfReferencing(row)) { 00196 // it's ok, after all. 00197 } else { 00198 throw new SQLException( 00199 "Foreign Key Constraint Violation: no parent: " + 00200 this.toString(), "23000"); 00201 } 00202 } 00203 } 00204 00205 /** 00206 * On insert, add the new 'export' constraint value 00207 */ 00208 public void applyInsert(Session session, Row row, long rowId, 00209 Constraint activeIndex) 00210 throws SQLException, IOException 00211 { 00212 byte[] key = makeKey(session, row, rowId); 00213 AddIndexEntry add = new AddIndexEntry(session, this, key, rowId); 00214 if (activeIndex == this) { 00215 session.addPendingAction(add); 00216 } else { 00217 session.doStep(add); 00218 } 00219 } 00220 00221 /** 00222 * On delete, remove the constraints holding the corresponding 00223 * row values. 00224 */ 00225 public void applyDelete(Session session, Row row, long rowId, 00226 Constraint activeIndex) 00227 throws SQLException, IOException 00228 { 00229 Database db = session.getDatabase(); 00230 byte[] key = makeKey(session, row, rowId); 00231 if (index == null) getIndex(db); 00232 DeleteIndexEntry del = new DeleteIndexEntry(session, this, key); 00233 if (activeIndex == this) { 00234 session.addPendingAction(del); 00235 } else { 00236 session.doStep(del); 00237 } 00238 } 00239 00240 /** 00241 * On update, if the imported key changes, we need to modify the 00242 * index. Once this happens, we create a session context in which 00243 * to record the index changes so we can do them all together, at 00244 * the end. 00245 */ 00246 public void checkUpdate(Session session, byte[] oldKey, Row row, 00247 Row oldRow, long rowId, Constraint activeIndex) 00248 throws SQLException, IOException 00249 { 00250 checkInsert(session, row); 00251 getComparator(); 00252 byte[] key = makeKey(session, row, rowId); 00253 if (activeIndex != this) oldKey = makeKey(session, oldRow, rowId); 00254 if (compare.compare(key, oldKey) != 0) { 00255 UpdateIndex ui = 00256 (UpdateIndex)session.getContext(this, isDeferred()); 00257 if (ui == null) { 00258 ui = new UpdateIndex(session, this); 00259 session.putContext(this, isDeferred(), ui); 00260 } 00261 ui.addEntry(key, oldKey, rowId); 00262 } 00263 } 00264 00265 /** 00266 * Check for the possibility that a row satisfies its own constraints. 00267 * It's a strange world we live in, get used to it. 00268 */ 00269 boolean checkSelfReferencing(Row row) throws SQLException { 00270 boolean anyNull = false; 00271 boolean allNull = true; 00272 boolean allMatch = true; 00273 for (int i = 0; i < fCols.length; i++) { 00274 Value v = row.item(fCols[i]); 00275 Value r = row.item(columns[i]); 00276 boolean isNull = Value.isNull(v); 00277 anyNull |= isNull; 00278 allNull &= isNull; 00279 if (allMatch) allMatch = Value.boolOp(Op.EQ, v, r); 00280 } 00281 if (allMatch || allNull) return true; 00282 if (anyNull && (spec & (Constraint.FULL | Constraint.PARTIAL)) != 0) 00283 return true; 00284 return false; 00285 } 00286 00287 /** 00288 * Get (create if necessary) the Btree for the foreign index. 00289 */ 00290 final Btree getForeignIndex(Session session) 00291 throws SQLException, IOException 00292 { 00293 if (fIndex == null) { 00294 fIndex = fConstraint.getIndex(session.getDatabase()); 00295 } 00296 return fIndex; 00297 } 00298 00299 /** 00300 * Get the export constraint if it exists already. 00301 */ 00302 public ExportedKeyConstraint findExportedKeyConstraint(Database db) 00303 throws IOException, SQLException 00304 { 00305 if (ec == null) { 00306 Table t = getFTable(db); 00307 String ecName = getExportConstraintName(); 00308 ec = (ExportedKeyConstraint)t.getConstraint(ecName); 00309 } 00310 return ec; 00311 } 00312 00313 /** 00314 * For the purposes of creating a unique key, the rowId is included, 00315 * whereas for purposes of checking foreign key integrity, the rowId 00316 * is ignored, only the key value matters. 00317 */ 00318 byte[] makeKey(Session session, Row row, long rowId) 00319 throws SQLException 00320 { 00321 return Key.makeKey(table, row, getColumns(), rowId, true); // !! 00322 } 00323 00324 /** 00325 * Get the index (create it if necessary). The key for this index includes 00326 * the actual key fields plus the row id to ensure uniqueness for Btreee 00327 * operations. Some operations require that we use a special comparator 00328 * which ignores the rowId part. 00329 */ 00330 public Btree getIndex(Database db) throws IOException { 00331 if (index == null) { 00332 index = new Btree(db.getFile(), indexRoot, false); 00333 } 00334 return index; 00335 } 00336 00337 /** 00338 * Externalizable: read me from a stream 00339 */ 00340 public void readExternal(ObjectInput in) 00341 throws IOException, ClassNotFoundException 00342 { 00343 super.readExternal(in); 00344 indexRoot = in.readLong(); 00345 } 00346 00347 /** 00348 * Externalizable: write me to a stream 00349 */ 00350 public void writeExternal(ObjectOutput out) throws IOException { 00351 super.writeExternal(out); 00352 out.writeLong(indexRoot); 00353 } 00354 00355 }