/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.ais.model; import com.foundationdb.ais.model.validation.AISInvariants; import java.util.HashMap; import java.util.List; import java.util.Map; public class ForeignKey implements Constraint { public static enum Action { NO_ACTION, RESTRICT, CASCADE, SET_NULL, SET_DEFAULT; public String toSQL() { return name().replace('_', ' '); } } public static ForeignKey create(AkibanInformationSchema ais, String constraintName, Table referencingTable, List<Column> referencingColumns, Table referencedTable, List<Column> referencedColumns, Action deleteAction, Action updateAction, boolean deferrable, boolean initiallyDeferred) { ais.checkMutability(); ForeignKey fk = new ForeignKey(constraintName, referencingTable, referencingColumns, referencedTable, referencedColumns, deleteAction, updateAction, deferrable, initiallyDeferred); AISInvariants.checkDuplicateConstraintsInSchema(ais, fk.getConstraintName()); referencingTable.addForeignKey(fk); referencedTable.addForeignKey(fk); if (constraintName != null) { ais.addConstraint(fk); } return fk; } /** Find an index on {@code table} starting with * {@code requiredColumns} in some order. */ public static TableIndex findIndex(Table table, List<Column> requiredColumns, boolean requireUnique) { int ncols = requiredColumns.size(); for (TableIndex index : table.getIndexesIncludingInternal()) { if (requireUnique) { if (!index.isUnique() || (index.getKeyColumns().size() != ncols)) { continue; } } else if (index.getKeyColumns().size() < ncols) { continue; } boolean found = true; for (int i = 0; i < ncols; i++) { if (requiredColumns.indexOf(index.getKeyColumns().get(i).getColumn()) < 0) { found = false; break; } } if (found) { return index; } } return null; } /** Find a unique index on <code>referencedTable</code> exactly * <code>referencedColumns</code> in some order. */ public static TableIndex findReferencedIndex(Table referencedTable, List<Column> referencedColumns) { return findIndex(referencedTable, referencedColumns, true); } /** Find an index on {@code referencingTable} starting with * {@code referencingColumns} in some order. */ public static TableIndex findReferencingIndex(Table referencingTable, List<Column> referencingColumns) { return findIndex(referencingTable, referencingColumns, false); } @Override public Table getConstraintTable() { return getReferencingTable(); } public TableName getConstraintName() { return constraintName; } public Table getReferencingTable() { return join.getChild(); } public List<Column> getReferencingColumns() { return join.getChildColumns(); } public TableIndex getReferencingIndex() { if (referencingIndex == null) { synchronized (this) { if (referencingIndex == null) { referencingIndex = findReferencingIndex(join.getChild(), join.getChildColumns()); } } } return referencingIndex; } public Table getReferencedTable() { return join.getParent(); } public List<Column> getReferencedColumns() { return join.getParentColumns(); } public TableIndex getReferencedIndex() { if (referencedIndex == null) { synchronized (this) { if (referencedIndex == null) { referencedIndex = findReferencedIndex(join.getParent(), join.getParentColumns()); } } } return referencedIndex; } public List<JoinColumn> getJoinColumns() { return join.getJoinColumns(); } public Action getDeleteAction() { return deleteAction; } public Action getUpdateAction() { return updateAction; } public boolean isDeferrable() { return deferrable; } public boolean isInitiallyDeferred() { return initiallyDeferred; } public boolean isDeferred(Map<ForeignKey,Boolean> transactionDeferred) { if (!deferrable) return false; if (transactionDeferred != null) { Boolean result = transactionDeferred.get(null); if (result != null) return result; result = transactionDeferred.get(this); if (result != null) return result; } return initiallyDeferred; } public static Map<ForeignKey,Boolean> setDeferred(Map<ForeignKey,Boolean> transactionDeferred, ForeignKey fkey, boolean deferred) { if (transactionDeferred == null) transactionDeferred = new HashMap<>(); if (fkey == null) { transactionDeferred.clear(); } else if(!fkey.isDeferrable()) { throw new IllegalArgumentException("Not deferrable: " + fkey); } transactionDeferred.put(fkey, deferred); return transactionDeferred; } @Override public String toString() { return "Foreign Key " + constraintName.getTableName() + ": " + join.getChild() + " REFERENCES " + join.getParent(); } private ForeignKey(String constraintName, Table referencingTable, List<Column> referencingColumns, Table referencedTable, List<Column> referencedColumns, Action deleteAction, Action updateAction, boolean deferrable, boolean initiallyDeferred) { this.constraintName = new TableName(referencingTable.getName().getSchemaName(), constraintName); this.deleteAction = deleteAction; this.updateAction = updateAction; this.deferrable = deferrable; this.initiallyDeferred = initiallyDeferred; join = Join.create(constraintName, referencedTable, referencingTable); for (int i = 0; i < referencingColumns.size(); i++) { join.addJoinColumn(referencedColumns.get(i), referencingColumns.get(i)); } } // NOTE: referencingColumns (join#childColumns) and // referencedColumns (join#parentColumns) are in // declaration order and parallel to one another. They are not // necessarily in the order of referencingIndex or // referencedIndex. private final TableName constraintName; private final Action deleteAction; private final Action updateAction; private final boolean deferrable, initiallyDeferred; private volatile TableIndex referencingIndex; private volatile TableIndex referencedIndex; private Join join; }