/** * 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.qp.rowtype; // Fields are untyped for now. Field name is just position within the type. import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.HKey; import com.foundationdb.ais.model.Table; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.explain.*; import com.foundationdb.server.types.value.ValueSource; public abstract class RowType { // Object interface @Override public String toString() { return String.format("RowType(%s)", typeId); } @Override public int hashCode() { return typeId * 9987001; } @Override public boolean equals(Object o) { return o == this || o != null && o instanceof RowType && this.typeId == ((RowType)o).typeId; } // RowType interface public abstract Schema schema(); public final int typeId() { return typeId; } public final TypeComposition typeComposition() { return typeComposition; } public final boolean ancestorOf(RowType that) { return that.typeComposition != null && this.typeComposition.isAncestorOf(that.typeComposition); } public final boolean parentOf(RowType that) { return that.typeComposition != null && this.typeComposition.isParentOf(that.typeComposition); } public abstract int nFields(); public abstract TInstance typeAt(int index); public HKey hKey() { return null; } /** * <p>Gets the AIS Column for a given field. This method may throw an exception if the RowType doesn't have * an AIS column at the given index, for whatever reason. This may be because the question doesn't make sense * ({@code SELECT 1;}), or because the implementation doesn't track information that it technically could.</p> * * <p>Regardless, you can invoke {@linkplain #fieldHasColumn(int)} to determine if this method will throw * an exception. If that method returns {@code true}, this method may not throw an exception.</p> * @param field the field index for which you want a column * @return the Column that corresponds to that field * @throws FieldHasNoColumnException if the field doesn't correspond to any Column * @throws IndexOutOfBoundsException if {@code field >= nFields() || field < 0} */ public Column fieldColumn(int field) { checkFieldRange(field); throw new FieldHasNoColumnException(field); } /** * <p>Returns whether the given field corresponds to a Column. If it does, {@linkplain #fieldColumn(int)} will * not throw an exception. If this method returns {@code false}, getting the field's column may (and probably will) * result in an exception.</p> * @param field the field for which you want a column * @return whether that field corresponds to a Column * @throws IndexOutOfBoundsException if {@code field >= nFields() || field < 0} */ public boolean fieldHasColumn(int field) { checkFieldRange(field); return false; } /** * <p>Get the one user table that this row type corresponds to. Not all row types correspond to one user table; * a flattened row doesn't, nor does a row type that adds fields. For instance, the final row type for a query * {@code SELECT cid FROM customer} may (but does not have to) have a corresponding Table; the row type * for {@code SELECT 1, cid FROM customer} may not, since the first field doesn't correspond to any column * in the {@code customer} table.</p> * * <p>If this row type doesn't correspond to a user table, it will throw an exception. You can test for that * using {@linkplain #hasTable()}. If that method returns true, this method may not throw an exception.</p> * * <p>If this method doesn't throw an exception, several other things must be true: * <ul> * <li>{@code fieldHasColumn(n) == true} for {@code 0 <= n < nFields()}</li> * <li>{@code fieldColumn(n).getTable() == table()} (for same range of {@code n}</li> * </ul></p> * @return the user table associated with this row * @throws RowTypeToTableMappingException if there is no user table associated with this row */ public Table table() { throw new RowTypeToTableMappingException("default RowType implementation has no Table"); } public boolean hasTable() { return false; } // Will want to override in most cases. public CompoundExplainer getExplainer(ExplainContext context) { Attributes atts = new Attributes(); atts.put(Label.NAME, PrimitiveExplainer.getInstance(toString())); return new CompoundExplainer(Type.ROWTYPE, atts); } // For use by subclasses protected void typeComposition(TypeComposition typeComposition) { this.typeComposition = typeComposition; } protected void checkFieldRange(int field) { if (field < 0) { throw new IndexOutOfBoundsException("field index must be >= 0: was " + field); } if (field >= nFields()) { throw new IndexOutOfBoundsException(String.format("field (%d) >= fields count (%d)", field, nFields())); } } protected RowType(int typeId) { this.typeId = typeId; } // Object state private final int typeId; private TypeComposition typeComposition; public final static class InconsistentRowTypeException extends RuntimeException{ /** * Expected null value * @param i the index in the row * @param value the value in the actual row */ public InconsistentRowTypeException(int i, Object value) { super("Value at " + i + " should be null, but was " + value); } public InconsistentRowTypeException(int i, TInstance rowType, TInstance valueType, ValueSource value) { super("value at index " + i + " expected type " + rowType + ", but UnderlyingType was " + valueType + ": " + value); } } }