/** * 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.qp.rowtype.InternalIndexTypes; import com.foundationdb.server.TableStatus; import com.foundationdb.server.rowdata.RowDef; import com.foundationdb.util.ArgumentValidation; import java.util.*; public class Table extends Columnar implements HasGroup, Visitable { public static Table create(AkibanInformationSchema ais, String schemaName, String tableName, Integer tableId) { Table table = new Table(ais, schemaName, tableName, tableId); ais.addTable(table); return table; } /** * Create an independent copy of an existing Table. * @param ais Destination AkibanInformationSchema. * @param table Table to copy. * @return The new copy of the Table. */ public static Table create(AkibanInformationSchema ais, Table table) { Table copy = create(ais, table.tableName.getSchemaName(), table.tableName.getTableName(), table.getTableId()); copy.setUuid(table.getUuid()); return copy; } private Table(AkibanInformationSchema ais, String schemaName, String tableName, Integer tableId) { super(ais, schemaName, tableName); this.tableId = tableId; this.groupIndexes = new HashSet<>(); this.unmodifiableGroupIndexes = Collections.unmodifiableCollection(groupIndexes); this.indexMap = new TreeMap<>(); this.unmodifiableIndexMap = Collections.unmodifiableMap(indexMap); this.fullTextIndexes = new HashSet<>(); this.unmodifiableFullTextIndexes = Collections.unmodifiableCollection(fullTextIndexes); this.foreignKeys = new LinkedHashSet<>(); this.unmodifiableForeignKeys = Collections.unmodifiableCollection(foreignKeys); } @Override public boolean isView() { return false; } public Integer getTableId() { return tableId; } /** * Temporary mutator so that prototype AIS management can renumber all * the tables once created. Longer term we want to give the table * its ID when generated. * * @param tableId */ public void setTableId(final int tableId) { this.tableId = tableId; } public Integer getOrdinal() { return ordinal; } public void setOrdinal(Integer ordinal) { this.ordinal = ordinal; } public Group getGroup() { return group; } public void setGroup(Group group) { this.group = group; } public Collection<TableIndex> getIndexesIncludingInternal() { return unmodifiableIndexMap.values(); } public Collection<TableIndex> getIndexes() { Collection<TableIndex> indexes = getIndexesIncludingInternal(); return removeInternalColumnIndexes(indexes); } public TableIndex getIndexIncludingInternal(String indexName) { return unmodifiableIndexMap.get(indexName); } public TableIndex getIndex(String indexName) { TableIndex index = null; if (indexName.equals(Index.PRIMARY)) { // getPrimaryKey has logic for handling hidden PK, needed to drop hidden pk PrimaryKey primaryKey = getPrimaryKey(); index = primaryKey == null ? null : primaryKey.getIndex(); } else { index = getIndexIncludingInternal(indexName); } return index; } /** * Get all GroupIndexes this table participates in, both explicit and implicit (i.e. as a declared column or * ancestor of a participating table). */ public final Collection<GroupIndex> getGroupIndexes() { return unmodifiableGroupIndexes; } protected synchronized void addIndex(TableIndex index) { indexMap.put(index.getIndexName().getName(), index); if (index.isPrimaryKey()) { assert primaryKey == null; primaryKey = new PrimaryKey(index); } } void clearIndexes() { indexMap.clear(); } final void addGroupIndex(GroupIndex groupIndex) { groupIndexes.add(groupIndex); } final void removeGroupIndex(GroupIndex groupIndex) { groupIndexes.remove(groupIndex); } public synchronized void removeIndexes(Collection<TableIndex> indexesToDrop) { if((primaryKey != null) && indexesToDrop.contains(primaryKey.getIndex())) { primaryKey = null; } indexMap.values().removeAll(indexesToDrop); } public void tableStatus(TableStatus tableStatus) { this.tableStatus = tableStatus; } public TableStatus tableStatus() { return this.tableStatus; } public void rowDef(RowDef rowDef) { this.rowDef = rowDef; } public RowDef rowDef() { return rowDef; } /** * Returns the columns in this table that are constrained to match the given column, e.g. * customer.cid and order.cid. These will be ordered by the table they appear on, root to leaf. * The given column will itself be in the resulting list. The list is calculated anew each time * and may be modified as needed by the caller. * @param column the column for which to find matching columns. * @return a new list of columns equivalent to the given column, including that column itself. */ List<Column> matchingColumns(Column column) { // TODO: make this a AISValidation check ArgumentValidation.isTrue(column + " doesn't belong to " + getName(), column.getTable() == this); List<Column> matchingColumns = new ArrayList<>(); matchingColumns.add(column); findMatchingAncestorColumns(column, matchingColumns); findMatchingDescendantColumns(column, matchingColumns); Collections.sort(matchingColumns, COLUMNS_BY_TABLE_DEPTH); return matchingColumns; } private void findMatchingAncestorColumns(Column fromColumn, List<Column> matchingColumns) { Join join = fromColumn.getTable().getParentJoin(); if (join != null) { JoinColumn ancestorJoinColumn = null; for (JoinColumn joinColumn : join.getJoinColumns()) { if (joinColumn.getChild() == fromColumn) { ancestorJoinColumn = joinColumn; } } if (ancestorJoinColumn != null) { Column ancestorColumn = ancestorJoinColumn.getParent(); matchingColumns.add(ancestorColumn); findMatchingAncestorColumns(ancestorJoinColumn.getParent(), matchingColumns); } } } private void findMatchingDescendantColumns(Column fromColumn, List<Column> matchingColumns) { for (Join join : getChildJoins()) { JoinColumn descendantJoinColumn = null; for (JoinColumn joinColumn : join.getJoinColumns()) { if (joinColumn.getParent() == fromColumn) { descendantJoinColumn = joinColumn; } } if (descendantJoinColumn != null) { Column descendantColumn = descendantJoinColumn.getChild(); matchingColumns.add(descendantColumn); join.getChild().findMatchingDescendantColumns(descendantJoinColumn.getChild(), matchingColumns); } } } public void addCandidateParentJoin(Join parentJoin) { candidateParentJoins.add(parentJoin); } public void addCandidateChildJoin(Join childJoin) { candidateChildJoins.add(childJoin); } public void removeCandidateParentJoin(Join parentJoin) { candidateParentJoins.remove(parentJoin); } public void removeCandidateChildJoin(Join childJoin) { candidateChildJoins.remove(childJoin); } public List<Join> getCandidateParentJoins() { return Collections.unmodifiableList(candidateParentJoins); } public List<Join> getCandidateChildJoins() { return Collections.unmodifiableList(candidateChildJoins); } public boolean hasChildren() { return !getCandidateChildJoins().isEmpty(); } public Table getParentTable() { Join j = getParentJoin(); return (j != null) ? j.getParent() : null; } public Join getParentJoin() { Join parentJoin = null; Group group = getGroup(); if (group != null) { for (Join candidateParentJoin : candidateParentJoins) { if (candidateParentJoin.getGroup() == group) { parentJoin = candidateParentJoin; } } } return parentJoin; } public List<Join> getChildJoins() { List<Join> childJoins = new ArrayList<>(); Group group = getGroup(); if (group != null) { for (Join candidateChildJoin : candidateChildJoins) { if (candidateChildJoin.getGroup() == group) { childJoins.add(candidateChildJoin); } } } return childJoins; } public Column getIdentityColumn() { Column identity = null; for (Column column : this.getColumnsIncludingInternal()) { if (column.getIdentityGenerator() != null) { identity = column; } } return identity; } public boolean isDescendantOf(Table other) { if (getGroup() == null || !getGroup().equals(other.getGroup())) { return false; } Table possibleDescendant = this; while (possibleDescendant != null) { if (possibleDescendant.equals(other)) { return true; } possibleDescendant = possibleDescendant.getParentTable(); } return false; } public synchronized PrimaryKey getPrimaryKey() { PrimaryKey declaredPrimaryKey = primaryKey; if (declaredPrimaryKey != null) { // TODO: This could be replace by a call to PrimaryKey#isAkibanPK() // But there is some dependecy here which causes the tests to fail if you do so. List<IndexColumn> pkColumns = primaryKey.getIndex().getKeyColumns(); if (pkColumns.size() == 1 && pkColumns.get(0).getColumn().isAkibanPKColumn()) { declaredPrimaryKey = null; } } return declaredPrimaryKey; } public synchronized PrimaryKey getPrimaryKeyIncludingInternal() { return primaryKey; } public synchronized void endTable(NameGenerator generator) { addHiddenPrimaryKey(generator); // Put the columns into our list TreeSet<String> entities = new TreeSet<String>(); for (Column column : getColumns()) { entities.add(column.getName()); } // put the child tables into their ordered list. TreeMap<String, Table> childTables = new TreeMap<>(); for (Join childJoin : candidateChildJoins ) { String childName; if (childJoin.getChild().getName().getSchemaName().equals(getName().getSchemaName())) { childName = childJoin.getChild().getName().getTableName(); } else { childName = childJoin.getChild().getName().toString(); } childTables.put(childName, childJoin.getChild()); } // Mangle the child table names to be unique with the "_" for (String child : childTables.keySet()) { String tryName = child; while (entities.contains(tryName)) { tryName = "_" + tryName; } childTables.get(child).nameForOutput = tryName; entities.add(tryName); } if (nameForOutput == null) { Join parentJoin = getParentJoin(); if ((parentJoin != null) && parentJoin.getParent().getName().getSchemaName().equals(getName().getSchemaName())) { nameForOutput = getName().getTableName(); } else { nameForOutput = getName().toString(); } } } public void addHiddenPrimaryKey(NameGenerator generator) { // Creates a PK for a pk-less table. if (primaryKey == null) { // Find primary key index TableIndex primaryKeyIndex = null; for (TableIndex index : getIndexesIncludingInternal()) { if (index.isPrimaryKey()) { primaryKeyIndex = index; } } if (primaryKeyIndex == null) { final int rootID; if(group == null) { rootID = getTableId(); } else { assert group.getRoot() != null : "Null root: " + group; rootID = group.getRoot().getTableId(); } primaryKeyIndex = createAkibanPrimaryKeyIndex(generator.generateIndexID(rootID), generator); } assert primaryKeyIndex != null : this; primaryKey = new PrimaryKey(primaryKeyIndex); } } public Integer getDepth() { if (depth == null && getGroup() != null) { synchronized (this) { if (depth == null && getGroup() != null) { depth = getParentJoin() == null ? 0 : getParentJoin().getParent().getDepth() + 1; } } } return depth; } public Boolean isRoot() { return getGroup() == null || getParentJoin() == null; } public HKey hKey() { assert getGroup() != null; if (hKey == null) { computeHKey(); } return hKey; } public List<Column> allHKeyColumns() { assert getGroup() != null; assert getPrimaryKeyIncludingInternal() != null; if (allHKeyColumns == null) { allHKeyColumns = new ArrayList<>(); for (HKeySegment segment : hKey().segments()) { for (HKeyColumn hKeyColumn : segment.columns()) { allHKeyColumns.add(hKeyColumn.column()); } } allHKeyColumns = Collections.unmodifiableList(allHKeyColumns); } return allHKeyColumns; } public boolean containsOwnHKey() { hKey(); // Ensure hKey and containsOwnHKey are computed return containsOwnHKey; } public UUID getUuid() { return uuid; } public void setUuid(UUID uuid) { this.uuid = uuid; } // Descendent tables whose hkeys are affected by a change to this table's PK or FK. public List<Table> hKeyDependentTables() { if (hKeyDependentTables == null) { synchronized (lazyEvaluationLock) { if (hKeyDependentTables == null) { List<Table> hKeyDependentTablesTmp = new ArrayList<>(); for (Join join : getChildJoins()) { Table child = join.getChild(); if (!child.containsOwnHKey()) { addTableAndDescendents(child, hKeyDependentTablesTmp); } } hKeyDependentTables = hKeyDependentTablesTmp; } } } return hKeyDependentTables; } public boolean isVirtual() { return (group != null) && group.isVirtual(); } public boolean hasVersion() { return version != null; } public Integer getVersion() { return version; } public void setVersion(Integer version) { this.version = version; } public String getNameForOutput() { return nameForOutput; } private void addTableAndDescendents(Table table, List<Table> accumulator) { accumulator.add(table); for (Join join : table.getChildJoins()) { addTableAndDescendents(join.getChild(), accumulator); } } private void computeHKey() { hKey = new HKey(this); List<Column> hKeyColumns = new ArrayList<>(); if (!isRoot()) { // Start with the parent's hkey Join join = getParentJoin(); HKey parentHKey = join.getParent().hKey(); // Start forming this table's full hkey by including all of the parent hkey columns, but replacing // columns participating in the join (to this table) by columns from this table. for (HKeySegment parentHKeySegment : parentHKey.segments()) { HKeySegment segment = hKey.addSegment(parentHKeySegment.table()); for (HKeyColumn parentHKeyColumn : parentHKeySegment.columns()) { Column columnInChild = join.getMatchingChild(parentHKeyColumn.column()); Column segmentColumn = columnInChild == null ? parentHKeyColumn.column() : columnInChild; segment.addColumn(segmentColumn); hKeyColumns.add(segmentColumn); } } } // This table's hkey also includes any PK columns not already included. HKeySegment newSegment = hKey.addSegment(this); for (Column pkColumn : getPrimaryKeyIncludingInternal().getColumns()) { if (!hKeyColumns.contains(pkColumn)) { newSegment.addColumn(pkColumn); } } // Determine whether the table contains its own hkey, i.e., whether all hkey columns come from this table. containsOwnHKey = true; for (HKeySegment segment : hKey().segments()) { for (HKeyColumn hKeyColumn : segment.columns()) { if (hKeyColumn.column().getTable() != this) { containsOwnHKey = false; } } } } private TableIndex createAkibanPrimaryKeyIndex(int indexID, NameGenerator generator) { // Create a column for a PK Column pkColumn = Column.create(this, Column.ROW_ID_NAME, getColumns().size(), InternalIndexTypes.LONG.instance(false)); // adds column to table // Create a sequence for the PK String schemaName = this.getName().getSchemaName(); // Generates same (temporary) sequence name as AISBuilder, // To catch (and reject) adding two sequences to the same table. TableName sequenceName = generator.generateIdentitySequenceName(ais, this.tableName, Column.ROW_ID_NAME); Sequence identityGenerator = Sequence.create(ais, schemaName, sequenceName.getTableName(), 1L, 1L, 0L, Long.MAX_VALUE, false); // Set column as PK using sequence pkColumn.setDefaultIdentity(false); pkColumn.setIdentityGenerator(identityGenerator); // Create Primary key TableName constraintName = generator.generatePKConstraintName(schemaName, tableName.getTableName()); TableIndex pkIndex = TableIndex.create(ais, this, Index.PRIMARY, indexID, true, true, constraintName); IndexColumn.create(pkIndex, pkColumn, 0, true, null); return pkIndex; } private static Collection<TableIndex> removeInternalColumnIndexes(Collection<TableIndex> indexes) { Collection<TableIndex> declaredIndexes = new ArrayList<>(indexes); for (Iterator<TableIndex> iterator = declaredIndexes.iterator(); iterator.hasNext();) { TableIndex index = iterator.next(); List<IndexColumn> indexColumns = index.getKeyColumns(); if (indexColumns.size() == 1 && indexColumns.get(0).getColumn().isAkibanPKColumn()) { iterator.remove(); } } return declaredIndexes; } public PendingOSC getPendingOSC() { return pendingOSC; } public void setPendingOSC(PendingOSC pendingOSC) { this.pendingOSC = pendingOSC; } /** Return all full text indexes in which this table participates. */ public Collection<FullTextIndex> getFullTextIndexes() { return unmodifiableFullTextIndexes; } /** Return full text indexes that index this table. */ public Collection<FullTextIndex> getOwnFullTextIndexes() { if (fullTextIndexes.isEmpty()) return Collections.emptyList(); Collection<FullTextIndex> result = new ArrayList<>(); for (FullTextIndex index : fullTextIndexes) { if (index.getIndexedTable() == this) { result.add(index); } } return result; } public void addFullTextIndex(FullTextIndex index) { fullTextIndexes.add(index); } /** Get a full text index this table participates in. */ public FullTextIndex getFullTextIndex(String indexName) { for (FullTextIndex index : fullTextIndexes) { if (index.getIndexName().getName().equals(indexName)) { return index; } } return null; } public Collection<ForeignKey> getForeignKeys() { return unmodifiableForeignKeys; } public Collection<ForeignKey> getReferencingForeignKeys() { if (foreignKeys.isEmpty()) return Collections.emptyList(); Collection<ForeignKey> result = new ArrayList<>(); for (ForeignKey fk : foreignKeys) { if (fk.getReferencingTable() == this) { result.add(fk); } } return result; } public ForeignKey getReferencingForeignKey(String name) { for (ForeignKey fk : foreignKeys) { if ((fk.getReferencingTable() == this) && fk.getConstraintName().getTableName().equals(name)) { return fk; } } return null; } public Collection<ForeignKey> getReferencedForeignKeys() { if (foreignKeys.isEmpty()) return Collections.emptyList(); Collection<ForeignKey> result = new ArrayList<>(); for (ForeignKey fk : foreignKeys) { if (fk.getReferencedTable() == this) { result.add(fk); } } return result; } public void addForeignKey(ForeignKey foreignKey) { ais.checkMutability(); foreignKeys.add(foreignKey); } public void removeForeignKey(ForeignKey foreignKey) { ais.checkMutability(); foreignKeys.remove(foreignKey); } // Visitable /** Visit this instance, every column, table index, full text index and then all children in depth first order. */ @Override public void visit(Visitor visitor) { visit(visitor, true); } /** As {@link #visit(Visitor)} but visit children a snapshot of children in breadth first order. */ public void visitBreadthFirst(Visitor visitor) { List<Table> remainingTables = new ArrayList<>(); List<Join> remainingJoins = new ArrayList<>(); remainingTables.add(this); remainingJoins.addAll(getCandidateChildJoins()); // Add before visit in-case visitor changes group or joins while(!remainingJoins.isEmpty()) { Join join = remainingJoins.remove(remainingJoins.size() - 1); Table child = join.getChild(); remainingTables.add(child); remainingJoins.addAll(child.getCandidateChildJoins()); } for(Table table : remainingTables) { table.visit(visitor, false); } } private void visit(Visitor visitor, boolean recurse) { visitor.visit(this); for(Column c : getColumnsIncludingInternal()) { c.visit(visitor); } for(Index i : getIndexesIncludingInternal()) { i.visit(visitor); } for(Index i : getOwnFullTextIndexes()) { i.visit(visitor); } if(recurse) { for(Join t : getChildJoins()) { t.getChild().visit(visitor, recurse); } } } // State private final Map<String, TableIndex> indexMap; private final Map<String, TableIndex> unmodifiableIndexMap; private final Collection<GroupIndex> groupIndexes; private final Collection<GroupIndex> unmodifiableGroupIndexes; private final List<Join> candidateParentJoins = new ArrayList<>(); private final List<Join> candidateChildJoins = new ArrayList<>(); private final Object lazyEvaluationLock = new Object(); private Group group; private Integer tableId; private RowDef rowDef; private TableStatus tableStatus; private Integer ordinal; private UUID uuid; private PrimaryKey primaryKey; private HKey hKey; private boolean containsOwnHKey; private List<Column> allHKeyColumns; private volatile Integer depth = null; private volatile List<Table> hKeyDependentTables; private Integer version; private PendingOSC pendingOSC; private final Collection<FullTextIndex> fullTextIndexes; private final Collection<FullTextIndex> unmodifiableFullTextIndexes; private String nameForOutput; private final Collection<ForeignKey> foreignKeys; private final Collection<ForeignKey> unmodifiableForeignKeys; // consts private static final Comparator<Column> COLUMNS_BY_TABLE_DEPTH = new Comparator<Column>() { @Override public int compare(Column o1, Column o2) { return o1.getTable().getDepth() - o2.getTable().getDepth(); } }; }