001: /*
002: * $Id: ForeignKeyConstraint.java,v 1.11 2007/11/13 19:04:02 rwald Exp $
003: * =======================================================================
004: * Copyright (c) 2002-2006 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040:
041: package org.axiondb.constraints;
042:
043: import java.util.ArrayList;
044: import java.util.Iterator;
045: import java.util.List;
046:
047: import org.axiondb.AxionException;
048: import org.axiondb.ColumnIdentifier;
049: import org.axiondb.Database;
050: import org.axiondb.Row;
051: import org.axiondb.RowDecorator;
052: import org.axiondb.RowIterator;
053: import org.axiondb.Selectable;
054: import org.axiondb.Table;
055: import org.axiondb.TableIdentifier;
056: import org.axiondb.TransactableTable;
057: import org.axiondb.engine.SnapshotIsolationTransaction;
058: import org.axiondb.engine.visitors.ResolveSelectableVisitor;
059: import org.axiondb.event.RowEvent;
060: import org.axiondb.event.RowInsertedEvent;
061:
062: /**
063: * A FOREIGN KEY constraint
064: *
065: * @version $Revision: 1.11 $ $Date: 2007/11/13 19:04:02 $
066: * @author Ahimanikya Satapathy
067: */
068: public class ForeignKeyConstraint extends BaseConstraint {
069:
070: public ForeignKeyConstraint(String name) {
071: this (name, "FOREIGN KEY");
072: _childColumns = new ArrayList<Selectable>();
073: _parentColumns = new ArrayList<Selectable>();
074: }
075:
076: public ForeignKeyConstraint(String name, String type) {
077: super (name, type);
078: }
079:
080: public void addColumns(List list) {
081: _childColumns = list;
082: }
083:
084: public void addForeignColumns(List list) {
085: _parentColumns = list;
086: }
087:
088: public boolean evaluate(RowEvent event) throws AxionException {
089: return evaluate(event, event.getTable().makeRowDecorator());
090: }
091:
092: public boolean evaluate(RowEvent event, RowDecorator dec)
093: throws AxionException {
094: if (null != event.getNewRow()) {
095: return handleInsertOrUpdate(event, dec);
096: } else if (null != event.getOldRow()) {
097: return handleDelete(event, dec);
098: }
099: return true; // otherwise
100: }
101:
102: public List getChildTableColumns() {
103: return _childColumns;
104: }
105:
106: public String getChildTableName() {
107: return _childTableName;
108: }
109:
110: public int getOnDeleteActionType() {
111: return _onDeleteActionType;
112: }
113:
114: public int getOnUpdateActionType() {
115: return _onUpdateActionType;
116: }
117:
118: public List getParentTableColumns() {
119: return _parentColumns;
120: }
121:
122: public String getParentTableName() {
123: return _parentTableName;
124: }
125:
126: // NOTE: Parent table columns has to be PK for now, we can UniqueConstraint later
127: public void resolve(Database db, TableIdentifier table)
128: throws AxionException {
129: if (!db.hasTable(getParentTableName())) {
130: throw new AxionException("Parent Table not found");
131: }
132:
133: // Resolve table names
134: _childTableName = table.getTableName();
135: _childTable = db.getTable(_childTableName);
136: _parentTable = db.getTable(getParentTableName());
137: resolveColumns();
138:
139: // Vlidate column size for both parent and child table
140: if (_childColumns.isEmpty()) {
141: throw new AxionException("Column reference not found...");
142: } else if (_childColumns.size() != _parentColumns.size()) {
143: throw new AxionException(
144: "parent-child columns don't match...");
145: }
146:
147: // resolve child table columns
148: TableIdentifier[] tables = toArray(table);
149: ResolveSelectableVisitor resolveSel = new ResolveSelectableVisitor(
150: db);
151: for (int i = 0, I = _childColumns.size(); i < I; i++) {
152: _childColumns.set(i, resolveSel.visit(
153: (Selectable) _childColumns.get(i), null, tables));
154: }
155:
156: // Resolve paranet table columns
157: tables = toArray(new TableIdentifier(getParentTableName()));
158: for (int i = 0, I = _childColumns.size(); i < I; i++) {
159: _parentColumns.set(i, resolveSel.visit(
160: (Selectable) _parentColumns.get(i), null, tables));
161: }
162: }
163:
164: public void setChildTable(Table table) {
165: _childTable = table;
166: }
167:
168: public void setChildTableName(String tableName) {
169: _childTableName = tableName;
170: }
171:
172: public void setOnDeleteActionType(int actionType) {
173: _onDeleteActionType = actionType;
174: }
175:
176: public void setOnUpdateActionType(int actionType) {
177: _onUpdateActionType = actionType;
178: }
179:
180: public void setParentTable(Table table) {
181: _parentTable = table;
182: }
183:
184: public void setParentTableName(String tableName) {
185: _parentTableName = tableName;
186: }
187:
188: private boolean handleDelete(RowEvent event, RowDecorator dec)
189: throws AxionException {
190: Table table = event.getTable();
191: dec.setRow(event.getOldRow());
192:
193: // Check whether Child table has a row that is referring row to be deleted
194: // If ON DELETE option is used we can either delete or set null the child row
195: if (table.getName().equals(getParentTableName())) {
196: List<Object> values = new ArrayList<Object>(_parentColumns
197: .size());
198: for (int i = 0, I = _parentColumns.size(); i < I; i++) {
199: values.add(((Selectable) _parentColumns.get(i))
200: .evaluate(dec));
201: }
202:
203: Table childTable = _childTable;
204: if (table instanceof TransactableTable) {
205: Iterator iter = ((TransactableTable) table)
206: .getTableModificationListeners();
207: while (iter.hasNext()) {
208: Object db = iter.next();
209: if (db instanceof SnapshotIsolationTransaction) {
210: childTable = ((SnapshotIsolationTransaction) db)
211: .getTable(getChildTableName());
212: break;
213: }
214: }
215: }
216:
217: RowIterator matching = childTable.getMatchingRows(
218: _childColumns, values, true);
219: if (matching.hasNext()) {
220: if (_onDeleteActionType == CASCADE
221: || _onDeleteActionType == SETNULL
222: || _onDeleteActionType == SETDEFAULT) {
223: return true;
224: } else {
225: return false;
226: }
227: } else {
228: return true;
229: }
230: }
231: return true;
232: }
233:
234: private boolean handleInsertOrUpdate(RowEvent event,
235: RowDecorator dec) throws AxionException {
236: Table table = event.getTable();
237: dec.setRow(event.getNewRow());
238:
239: // Check whether Parent table has a row that matched the FK value
240: // FKs are valid if one or more of the columns are NULL
241: if (table.getName().equals(getChildTableName())) {
242: List<Object> values = new ArrayList<Object>(_childColumns
243: .size());
244: for (int i = 0, I = _childColumns.size(); i < I; i++) {
245: ColumnIdentifier colid = (ColumnIdentifier) _childColumns
246: .get(i);
247: Object val = colid.evaluate(dec);
248: if (val == null
249: || val.equals(_childTable.getColumn(
250: colid.getName()).getDefault())) {
251: return true;
252: }
253: values.add(val);
254: }
255:
256: Table parentTable = _parentTable;
257: if (table instanceof TransactableTable) {
258: Iterator iter = ((TransactableTable) table)
259: .getTableModificationListeners();
260: while (iter.hasNext()) {
261: Object db = iter.next();
262: if (db instanceof SnapshotIsolationTransaction) {
263: parentTable = ((SnapshotIsolationTransaction) db)
264: .getTable(getParentTableName());
265: break;
266: }
267: }
268: }
269:
270: RowIterator matching = parentTable.getMatchingRows(
271: _parentColumns, values, true);
272: if (matching.hasNext()) {
273: return true;
274: } else {
275: return false;
276: }
277: }
278: return true;
279: }
280:
281: // TODO: We can (for sure) do better than this, I am too lazy now ;) -- Ahi
282: public boolean evaluate(RowIterator oldRows, RowIterator newRows,
283: Table table) throws AxionException {
284: if (null == newRows || newRows.isEmpty()) {
285: return true;
286: }
287:
288: RowDecorator dec = table.makeRowDecorator();
289: Row oldRow = null;
290: newRows.reset();
291: if (oldRows != null) {
292: oldRows.reset();
293: }
294: while (newRows.hasNext()) {
295: oldRow = (null != oldRows && oldRows.hasNext() ? oldRows
296: .next() : null);
297: RowEvent event = new RowInsertedEvent(table, oldRow,
298: newRows.next());
299: if (!evaluate(event, dec)) {
300: return false;
301: }
302: }
303: return true;
304: }
305:
306: private boolean matchColumns(List tableCols, List pkCols) {
307: if (tableCols.size() != pkCols.size()) {
308: return false;
309: }
310:
311: for (int i = 0, I = pkCols.size(); i < I; i++) {
312: ColumnIdentifier colid = (ColumnIdentifier) tableCols
313: .get(i);
314: ColumnIdentifier pkcolid = (ColumnIdentifier) pkCols.get(i);
315: if (!colid.getName().equals(pkcolid.getName())) {
316: return false;
317: }
318: }
319:
320: return true;
321: }
322:
323: private void resolveColumns() throws AxionException {
324: boolean foundKey = false;
325: UniqueConstraint uc = null;
326:
327: // If primary key exist in parent table, choose that column as refernce column,
328: // when reference columns are not specified.
329: for (Iterator iter = _parentTable.getConstraints(); !foundKey
330: && iter != null && iter.hasNext();) {
331: Object constraint = iter.next();
332: if (constraint instanceof PrimaryKeyConstraint) {
333: uc = (UniqueConstraint) constraint;
334: foundKey = resolveColumn(uc);
335: }
336: }
337:
338: // otherwise try any Unique Constraint for a match
339: for (Iterator iter = _parentTable.getConstraints(); !foundKey
340: && iter != null && iter.hasNext();) {
341: Object constraint = iter.next();
342: if (constraint instanceof UniqueConstraint) {
343: uc = (UniqueConstraint) constraint;
344: foundKey = resolveColumn(uc);
345: }
346: }
347:
348: if (!foundKey) {
349: throw new AxionException(
350: "Primary/Unique Key Constraint not found for the given keys in parent table");
351: } else {
352: uc.addFK(getName());
353: }
354: }
355:
356: private boolean resolveColumn(UniqueConstraint uc) {
357: boolean foundKey = false;
358: if (_parentColumns.isEmpty() && !_childColumns.isEmpty()
359: && _childColumns.size() == uc.getSelectableCount()) {
360: for (int i = 0, I = _childColumns.size(); i < I; i++) {
361: _parentColumns.add(new ColumnIdentifier(
362: ((ColumnIdentifier) uc.getSelectable(i))
363: .getName()));
364: }
365: foundKey = true;
366: } else if (_childColumns.isEmpty() && !_parentColumns.isEmpty()
367: && _parentColumns.size() == uc.getSelectableCount()) {
368: for (int i = 0, I = _parentColumns.size(); i < I; i++) {
369: _childColumns.add(new ColumnIdentifier(
370: ((ColumnIdentifier) uc.getSelectable(i))
371: .getName()));
372: }
373: foundKey = true;
374: } else if (_childColumns.isEmpty() && _parentColumns.isEmpty()) {
375: for (int i = 0, I = uc.getSelectableCount(); i < I; i++) {
376: _parentColumns.add(new ColumnIdentifier(
377: ((ColumnIdentifier) uc.getSelectable(i))
378: .getName()));
379: _childColumns.add(new ColumnIdentifier(
380: ((ColumnIdentifier) uc.getSelectable(i))
381: .getName()));
382: }
383: foundKey = true;
384: } else if (!_childColumns.isEmpty()
385: && !_parentColumns.isEmpty()
386: && _childColumns.size() == _parentColumns.size()
387: && matchColumns(_parentColumns, uc.getSelectableList())) {
388: foundKey = true;
389: }
390: return foundKey;
391: }
392:
393: public static final int CASCADE = 10;
394: public static final int RESTRICT = 40;
395: public static final int SETDEFAULT = 30;
396: public static final int SETNULL = 20;
397:
398: private static final long serialVersionUID = -54506312013696264L;
399:
400: private List _childColumns;
401: private transient Table _childTable;
402: private String _childTableName;
403: private int _onDeleteActionType = RESTRICT;
404: private int _onUpdateActionType = RESTRICT;
405: private List _parentColumns;
406: private transient Table _parentTable;
407: private String _parentTableName;
408: }
|