/** * 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 com.foundationdb.qp.storeadapter.SpatialHelper; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.aksql.aktypes.AkBlob; import com.foundationdb.server.types.common.types.TBigDecimal; import com.foundationdb.server.types.common.types.TBinary; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.mcompat.mtypes.MNumeric; import com.foundationdb.server.spatial.Spatial; import com.geophile.z.Space; import java.util.*; public abstract class Index extends HasStorage implements Visitable, Constraint { public abstract HKey hKey(); public abstract boolean isTableIndex(); public abstract void computeFieldAssociations(Map<Table,Integer> ordinalMap); public abstract Table leafMostTable(); public abstract Table rootMostTable(); public abstract void checkMutability(); public abstract Collection<Integer> getAllTableIDs(); protected Index(TableName tableName, String indexName, Integer indexId, Boolean isUnique, Boolean isPrimary, TableName constraintName, JoinType joinType) { AISInvariants.checkNullName(indexName, "index", "index name"); if(((isUnique == null) || !isUnique) && (constraintName != null)) { throw new IllegalStateException("Unexpected constraint name for non-unique index: " + indexName); } this.indexName = new IndexName(tableName, indexName); this.indexId = indexId; this.isUnique = isUnique; this.isPrimary = isPrimary; this.joinType = joinType; this.constraintName = constraintName; this.keyColumns = new ArrayList<>(); this.indexMethod = IndexMethod.NORMAL; } public boolean isGroupIndex() { return !isTableIndex(); } public JoinType getJoinType() { return joinType; } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(getTypeString()); buffer.append("("); buffer.append(getNameString()); buffer.append(keyColumns.toString()); buffer.append(")"); if (space != null) { buffer.append(space.toString()); } return buffer.toString(); } void addColumn(IndexColumn indexColumn) { if (columnsFrozen) { throw new IllegalStateException("can't add column because columns list is frozen"); } keyColumns.add(indexColumn); columnsStale = true; } public void freezeColumns() { if (!columnsFrozen) { sortColumnsIfNeeded(); columnsFrozen = true; } } public boolean isUnique() { return isUnique; } public boolean isPrimaryKey() { return isPrimary; } public IndexName getIndexName() { return indexName; } public void setIndexName(IndexName name) { indexName = name; } /** * Return columns declared as part of the index definition. * @return list of columns */ public List<IndexColumn> getKeyColumns() { sortColumnsIfNeeded(); return keyColumns; } /** * Return all columns that make up the physical index key. This includes declared columns and hkey columns. * @return list of columns */ public List<IndexColumn> getAllColumns() { return allColumns; } public IndexMethod getIndexMethod() { return indexMethod; } public void markSpatial(int firstSpatialArgument, int spatialColumns, IndexMethod indexMethod) { checkMutability(); if (spatialColumns != Spatial.LAT_LON_DIMENSIONS && spatialColumns != 1) { // Either 1 or 2 is acceptable for now. 1: A blob containing a serialized spatial object. 2: lat/lon throw new IllegalArgumentException(); } this.firstSpatialArgument = firstSpatialArgument; this.lastSpatialArgument = firstSpatialArgument + spatialColumns - 1; this.space = Spatial.createLatLonSpace(); this.indexMethod = indexMethod; } public int firstSpatialArgument() { assert isSpatial(); return firstSpatialArgument; } public int lastSpatialArgument() { assert isSpatial(); return lastSpatialArgument; } public int dimensions() { // Only lat/lon for now return Spatial.LAT_LON_DIMENSIONS; } public int spatialColumns() { // For lat/lon, this computes 2. For blobs storing spatial objects, 1. // Non-spatial: 0 return isSpatial() ? lastSpatialArgument - firstSpatialArgument + 1 : 0; } public Space space() { return space; } public final boolean isSpatial() { return indexMethod.isSpatial(); } private void sortColumnsIfNeeded() { if (columnsStale) { Collections.sort(keyColumns, new Comparator<IndexColumn>() { @Override public int compare(IndexColumn x, IndexColumn y) { return x.getPosition() - y.getPosition(); } }); columnsStale = false; } } public Integer getIndexId() { return indexId; } public void setIndexId(Integer indexId) { this.indexId = indexId; } public IndexType getIndexType() { return isTableIndex() ? IndexType.TABLE : IndexType.GROUP; } public IndexRowComposition indexRowComposition() { return indexRowComposition; } protected static class AssociationBuilder { /** * @param fieldPosition entry of {@link IndexRowComposition#fieldPositions} * @param hkeyPosition entry of {@link IndexRowComposition#hkeyPositions} */ void rowCompEntry(int fieldPosition, int hkeyPosition) { list1.add(fieldPosition); list2.add(hkeyPosition); } /** * @param ordinal entry of {@link IndexToHKey#ordinals} * @param indexRowPosition entry of {@link IndexToHKey#indexRowPositions} */ void toHKeyEntry(int ordinal, int indexRowPosition) { list1.add(ordinal); list2.add(indexRowPosition); } IndexRowComposition createIndexRowComposition() { return new IndexRowComposition(asArray(list1), asArray(list2)); } IndexToHKey createIndexToHKey() { return new IndexToHKey(asArray(list1), asArray(list2)); } private int[] asArray(List<Integer> list) { int[] array = new int[list.size()]; for(int i = 0; i < list.size(); ++i) { array[i] = list.get(i); } return array; } private List<Integer> list1 = new ArrayList<>(); private List<Integer> list2 = new ArrayList<>(); } public boolean containsTableColumn(TableName tableName, String columnName) { for(IndexColumn iCol : keyColumns) { Column column = iCol.getColumn(); if(column.getTable().getName().equals(tableName) && column.getName().equals(columnName)) { return true; } } return false; } // Visitable /** Visit this instance and then all index columns. */ @Override public void visit(Visitor visitor) { visitor.visit(this); // Not present until computeFieldAssociations is called List<IndexColumn> cols = (allColumns == null) ? keyColumns : allColumns; for(IndexColumn ic : cols) { ic.visit(visitor); } } // akCollators and types provide type info for physical index rows. // Physical != logical for spatial indexes. public TInstance[] types() { ensureTypeInfo(); return types; } private void ensureTypeInfo() { if (types == null) { synchronized (this) { if (types == null) { int physicalColumns; int firstSpatialColumn; if (isSpatial()) { physicalColumns = allColumns.size() - spatialColumns() + 1; firstSpatialColumn = firstSpatialArgument(); } else { physicalColumns = allColumns.size(); firstSpatialColumn = Integer.MAX_VALUE; } TInstance[] localTInstances = null; localTInstances = new TInstance[physicalColumns]; int logicalColumn = 0; int physicalColumn = 0; int nColumns = allColumns.size(); while (logicalColumn < nColumns) { if (logicalColumn == firstSpatialColumn) { localTInstances[physicalColumn] = MNumeric.BIGINT.instance(SpatialHelper.isNullable(this)); logicalColumn += spatialColumns(); } else { IndexColumn indexColumn = allColumns.get(logicalColumn); Column column = indexColumn.getColumn(); localTInstances[physicalColumn] = column.getType(); logicalColumn++; } physicalColumn++; } types = localTInstances; } } } } public static boolean isSpatialCompatible(Index index) { boolean isSpatialCompatible = false; if (index.isSpatial()) { if (index.firstSpatialArgument() == index.lastSpatialArgument()) { // Serialized spatial object isSpatialCompatible = isTextOrBinary(index.getKeyColumns().get(index.firstSpatialArgument()).getColumn()); } else { // Lat/Lon isSpatialCompatible = true; for (int d = index.firstSpatialArgument(); d <= index.lastSpatialArgument(); d++) { isSpatialCompatible = isSpatialCompatible && isFixedDecimal(index.getKeyColumns().get(d).getColumn()); } } } return isSpatialCompatible; } private static boolean isFixedDecimal(Column column) { return column.getType().typeClass() instanceof TBigDecimal; } private static boolean isTextOrBinary(Column column) { TClass columnType = column.getType().typeClass(); // TBD: Is this right? What types can store serialized spatial objects? return columnType instanceof TBinary || columnType instanceof TString || columnType instanceof AkBlob; } public static final String PRIMARY = "PRIMARY"; private final Boolean isUnique; private final Boolean isPrimary; private final JoinType joinType; private Integer indexId; private IndexName indexName; private boolean columnsStale = true; private boolean columnsFrozen = false; protected IndexRowComposition indexRowComposition; protected List<IndexColumn> keyColumns; protected List<IndexColumn> allColumns; private volatile TInstance[] types; private TableName constraintName; private IndexMethod indexMethod; // For a spatial index private Space space; private int firstSpatialArgument; private int lastSpatialArgument; public enum JoinType { LEFT, RIGHT } public static enum IndexType { TABLE("TABLE"), GROUP("GROUP"), FULL_TEXT("FULL_TEXT") ; private IndexType(String asString) { this.asString = asString; } @Override public final String toString() { return asString; } private final String asString; } public enum IndexMethod { NORMAL(false), GEO_LAT_LON(true), GEO_WKB(true), GEO_WKT(true), FULL_TEXT(false); public boolean isSpatial() { return isSpatial; } private IndexMethod(boolean isSpatial) { this.isSpatial = isSpatial; } private final boolean isSpatial; } // HasStorage @Override public AkibanInformationSchema getAIS() { return leafMostTable().getAIS(); } @Override public String getTypeString() { return "Index"; } @Override public String getNameString() { return indexName.toString(); } @Override public String getSchemaName() { return indexName.getSchemaName(); } // constraint @Override public Table getConstraintTable() { return null; } @Override public TableName getConstraintName() { return constraintName; } }