/** * 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.row; import java.util.Arrays; import java.util.EnumMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.foundationdb.ais.model.HKeyColumn; import com.foundationdb.ais.model.HKeySegment; import com.foundationdb.ais.model.Table; import com.foundationdb.qp.rowtype.HKeyRowType; import com.foundationdb.server.store.PersistitKeyAppender; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TKeyComparable; import com.foundationdb.server.types.service.TypesRegistryService; import com.foundationdb.server.types.value.UnderlyingType; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.ValueTargets; import com.persistit.Key; public class ValuesHKey extends AbstractValuesHolderRow implements HKey { // HKey interface - Object interface @Override public boolean equals(Object that) { return that == this || (that != null && that instanceof ValuesHKey && this.compareTo((ValuesHKey)that) == 0); } @Override // TODO: This is overkill, but several tests rely upon the older PersistitHKey(key#toString()) behavior, // See ITBase#compareTwoRows(line 190ff). public String toString() { Key target = new Key (null, 2047); this.copyTo(target); return target.toString(); } // HKey Interface - Comparable interface @Override public int compareTo(HKey o) { assert o instanceof ValuesHKey : "Wrong HKey Type for compareTo"; ValuesHKey that = (ValuesHKey)o; int columnIndex = 0; int cmp; for (int segment = 0; segment < hKeySegments; segment++) { cmp = this.ordinals[segment] - that.ordinals[segment]; if (cmp != 0) { return cmp; } for (int i = 0; i < rowType().hKey().segments().get(segment).columns().size(); i++ ) { if (!that.values.get(columnIndex).hasAnyValue()) { return 1; } else if (!this.values.get(columnIndex).hasAnyValue()) { return -1; } else { ValueSource left = this.values.get(columnIndex); ValueSource right = that.values.get(columnIndex); TKeyComparable compare = registry.getKeyComparable(this.values.get(columnIndex).getType().typeClass(), that.values.get(columnIndex).getType().typeClass()); if (compare != null) { cmp = compare.getComparison().compare(left.getType(), left, right.getType(), right); } else { cmp = TClass.compare(left.getType(), left, right.getType(), right); } if (cmp != 0) { return cmp; } } } columnIndex++; } return 0; } // HKey interface implementation @Override public boolean prefixOf(HKey hKey) { assert hKey instanceof ValuesHKey : "Wrong HKey Type for prefixOf"; ValuesHKey that = (ValuesHKey)hKey; if (this.rowType().table().getGroup() != that.rowType().table().getGroup()) return false; if (this.hKeySegments <= that.hKeySegments) { int cmp = compareTo(that); if (cmp == 0) return true; } return false; } @Override public int segments() { return hKeySegments; } @Override public void useSegments(int segments) { assert segments > 0 && segments < ordinals.length : segments; hKeySegments = segments; } @Override public void copyTo(HKey target) { assert target instanceof ValuesHKey : "Wrong HKey Type for copyTo"; ValuesHKey t = (ValuesHKey)target; int columnIndex = 0; for (int segment = 0; segment < hKeySegments; segment++) { if (segment >= t.hKeySegments) break; assert ordinals[segment] == t.ordinals[segment] : "Mismatched ordinals on segment " + segment +" for " + ordinals[segment] + " source vs " + t.ordinals[segment] + " target"; for (int i = 0 ; i < keyDepth[segment]; i++) { copyValue (valueAt(columnIndex), t.valueAt(columnIndex)); columnIndex++; } } if (t.hKeySegments > hKeySegments) t.useSegments(hKeySegments); } public void copyValueTo(ValueSource value, int columnIndex) { copyValue (value, valueAt(columnIndex)); } @Override public void extendWithOrdinal(int ordinal) { // There are two cases where we want to extend this HKey // This (child hkey) was copied from parent, and now extended to match this child if (hKeySegments < ordinals.length-1 ) { //LOG.trace("Attempting to assign ordinal " + ordinal + " to position " + hKeySegments + " into " + Arrays.toString(ordinals)); int finalSegment = 0; for (int segment = hKeySegments - 1; segment < ordinals.length-1; segment++) { if (ordinals[segment] == ordinal) finalSegment = segment; } assert finalSegment != 0 : "Attempting to assign ordinal " + ordinal + " to position " + hKeySegments + " into " + Arrays.toString(ordinals); for (int segment = hKeySegments; segment < finalSegment; segment++) { hKeySegments++; extendWithNull(); } hKeySegments++; } else { // This (parent hkey) is being extended to query a specific child ordinals[hKeySegments++] = ordinal; } } @Override public void extendWithNull() { int columnIndex = -1; int segment; for (segment = 0; segment < hKeySegments; segment++) { columnIndex += keyDepth[segment]; } for (int i = 0; i < keyDepth[segment-1] && columnIndex < values.size() ; columnIndex++, i++) { valueAt(columnIndex).putNull(); } } // HKey interface implementation - low level interface (to be removed at some point) @Override public void copyTo (Key target) { int columnIndex = 0; PersistitKeyAppender appender = PersistitKeyAppender.create(target, rowType().table().getName() ); int maxSegments = Math.min(hKeySegments, rowType().hKey().segments().size()); for (int segment = 0; segment < maxSegments; segment++) { appender.append(ordinals[segment]); for (HKeyColumn column : rowType().hKey().segments().get(segment).columns()) { if (this.values.get(columnIndex).hasAnyValue()) { appender.append(this.values.get(columnIndex), column.column()); } columnIndex++; } } } @Override public void copyFrom(Key source) { int sourceDepth = source.getDepth(); assert sourceDepth >= 1 : "No data in source key"; source.indexTo(0); int segment = 0; int columnsInSegment = 0; int ordinal = source.decodeInt(); sourceDepth--; assert ordinal == ordinals[segment] : "Wrong ordinal for segment from key"; for (int i = 0; i < values.size(); i++) { if (columnsInSegment == keyDepth[segment] ) { segment++; ordinal = source.decodeInt(); assert ordinals[segment] == ordinal : "Mismatched ordinals " + ordinals[segment] + " source vs " + ordinal + " target"; columnsInSegment = 0; sourceDepth--; } if (sourceDepth <= 0|| source.isNull(true)) { values.get(i).putNull(); } else { sourceDepth--; Value valueTarget = values.get(i); UnderlyingType underlyingType = TInstance.underlyingType(valueTarget.getType()); Class<?> expected = underlyingExpectedClasses.get(underlyingType); if (source.decodeType() == expected) { switch (underlyingType) { case BOOL: valueTarget.putBool(source.decodeBoolean()); break; case INT_8: valueTarget.putInt8((byte)source.decodeLong()); break; case INT_16: valueTarget.putInt16((short)source.decodeLong()); break; case UINT_16: valueTarget.putUInt16((char)source.decodeLong()); break; case INT_32: valueTarget.putInt32((int)source.decodeLong()); break; case INT_64: valueTarget.putInt64(source.decodeLong()); break; case FLOAT: valueTarget.putFloat(source.decodeFloat()); break; case DOUBLE: valueTarget.putDouble(source.decodeDouble()); break; case BYTES: valueTarget.putBytes(source.decodeByteArray()); break; case STRING: valueTarget.putString(source.decodeString(), null); break; default: throw new UnsupportedOperationException(valueTarget.getType() + " with " + underlyingType); } } else { valueTarget.putObject(source.decode()); } // the following assumes that the TClass' readCollating expects the same UnderlyingType for in and out valueTarget.getType().readCollating(valueTarget, valueTarget); } columnsInSegment++; } } public void copyFrom(byte[] source) { Key key = new Key(null, 2047); key.setEncodedSize(source.length); System.arraycopy(source, 0, key.getEncodedBytes(), 0, source.length); this.copyFrom(key); } public byte[] hKeyBytes() { Key key = new Key (null, 2047); this.copyTo(key); byte[] bytes = new byte[key.getEncodedSize()]; System.arraycopy(key.getEncodedBytes(), 0, bytes, 0, bytes.length); return bytes; } // AbstractHoldersRow overrides @Override public HKey hKey() { return this; } public Value valueAt(int index) { return super.valueAt(index); } @Override public HKey ancestorHKey(Table table) { HKeyRowType rowType = this.rowType().schema().newHKeyRowType(table.hKey()); HKey ancestorHKey = new ValuesHKey(rowType, this.registry); copyTo(ancestorHKey); ancestorHKey.useSegments(table.getDepth() + 1); return ancestorHKey; } // Constructors and private methods. public ValuesHKey (HKeyRowType rowType, TypesRegistryService registry) { super(rowType, true); this.registry = registry; this.hKeySegments = rowType().hKey().segments().size(); setOrdinals(); } private void setOrdinals() { this.ordinals = new int [hKeySegments+1]; this.keyDepth = new int [hKeySegments]; int ordinalIndex = 0; for (HKeySegment hKeySegment : rowType().hKey().segments()) { Table segmentTable = hKeySegment.table(); ordinals[ordinalIndex] = segmentTable.getOrdinal(); keyDepth[ordinalIndex] = hKeySegment.columns().size(); ordinalIndex++; } } private void copyValue (ValueSource valueSource, Value valueTarget) { if (valueSource.hasAnyValue()) { if (valueSource.isNull()) { valueTarget.putNull(); } else { TKeyComparable compare = registry.getKeyComparable(valueSource.getType().typeClass(), valueTarget.getType().typeClass()); if (compare != null) { compare.getComparison().copyComparables(valueSource, valueTarget); } else { ValueTargets.copyFrom(valueSource, valueTarget); } } } } // For testing purposes public int[] ordinals() { return ordinals; } private int[] ordinals; private int[] keyDepth; private int hKeySegments; private static final Logger LOG = LoggerFactory.getLogger(ValuesHKey.class); private final TypesRegistryService registry; private static final EnumMap<UnderlyingType, Class<?>> underlyingExpectedClasses = createPUnderlyingExpectedClasses(); private static EnumMap<UnderlyingType, Class<?>> createPUnderlyingExpectedClasses() { EnumMap<UnderlyingType, Class<?>> result = new EnumMap<>(UnderlyingType.class); for (UnderlyingType underlyingType : UnderlyingType.values()) { final Class<?> expected; switch (underlyingType) { case BOOL: expected = Boolean.class; break; case INT_8: case INT_16: case UINT_16: case INT_32: case INT_64: expected = Long.class; break; case FLOAT: expected = Float.class; break; case DOUBLE: expected = Double.class; break; case BYTES: expected = byte[].class; break; case STRING: expected = String.class; break; default: throw new AssertionError("unrecognized UnderlyingType: " + underlyingType); } result.put(underlyingType, expected); } return result; } }