/** * 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.server.store.format; import com.foundationdb.ais.model.HasStorage; import com.foundationdb.ais.model.StorageDescription; import com.foundationdb.ais.model.validation.AISValidationFailure; import com.foundationdb.ais.model.validation.AISValidationOutput; import com.foundationdb.ais.protobuf.AISProtobuf.Storage; import com.foundationdb.ais.protobuf.FDBProtobuf; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.row.OverlayingRow; import com.foundationdb.server.error.AkibanInternalException; import com.foundationdb.server.error.StorageDescriptionInvalidException; import com.foundationdb.server.service.session.Session; import com.foundationdb.server.store.FDBScanTransactionOptions; import com.foundationdb.server.store.FDBStore; import com.foundationdb.server.store.FDBStoreData; import com.foundationdb.server.store.FDBStoreDataHelper; import com.foundationdb.server.store.FDBStoreDataKeyValueIterator; import com.foundationdb.server.store.FDBStoreDataSingleKeyValueIterator; import com.foundationdb.server.store.FDBTransactionService.TransactionState; import com.foundationdb.server.store.StoreStorageDescription; import com.foundationdb.server.types.aksql.aktypes.AkBlob; import com.foundationdb.server.service.blob.BlobRef; import com.foundationdb.KeySelector; import com.foundationdb.Transaction; import com.foundationdb.async.Future; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.tuple.ByteArrayUtil; import com.foundationdb.tuple.Tuple2; import com.google.protobuf.ByteString; import com.persistit.Key; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.foundationdb.server.store.FDBStoreDataHelper.*; /** Storage using the FDB directory layer. * As a result, there is no possibility of duplicate names and no need * of name generation. */ public class FDBStorageDescription extends StoreStorageDescription<FDBStore,FDBStoreData> { private byte[] prefixBytes; private static final Logger LOG = LoggerFactory.getLogger(FDBStorageDescription.class); public FDBStorageDescription(HasStorage forObject, String storageFormat) { super(forObject, storageFormat); } public FDBStorageDescription(HasStorage forObject, byte[] prefixBytes, String storageFormat) { super(forObject, storageFormat); this.prefixBytes = prefixBytes; } public FDBStorageDescription(HasStorage forObject, FDBStorageDescription other, String storageFormat) { super(forObject, other, storageFormat); this.prefixBytes = other.prefixBytes; } @Override public StorageDescription cloneForObject(HasStorage forObject) { return new FDBStorageDescription(forObject, this, storageFormat); } @Override public StorageDescription cloneForObjectWithoutState(HasStorage forObject) { return new FDBStorageDescription(forObject, storageFormat); } @Override public void writeProtobuf(Storage.Builder builder) { if(prefixBytes != null) { builder.setExtension(FDBProtobuf.prefixBytes, ByteString.copyFrom(prefixBytes)); } writeUnknownFields(builder); } public byte[] getPrefixBytes() { return prefixBytes; } protected void setPrefixBytes(byte[] prefixBytes) { this.prefixBytes = prefixBytes; } /** Compare prefix byte-by-byte for uniqueness. */ final class UniqueKey { byte[] getPrefixBytes() { return FDBStorageDescription.this.prefixBytes; } @Override public boolean equals(Object other) { return (other instanceof UniqueKey) && Arrays.equals(prefixBytes, ((UniqueKey)other).getPrefixBytes()); } @Override public int hashCode() { return Arrays.hashCode(prefixBytes); } @Override public String toString() { return getNameString(); } } @Override public Object getUniqueKey() { if (prefixBytes == null) return null; return new UniqueKey(); } @Override public String getNameString() { return (prefixBytes != null) ? ByteArrayUtil.printable(prefixBytes) : null; } @Override public void validate(AISValidationOutput output) { if (prefixBytes == null) { output.reportFailure(new AISValidationFailure(new StorageDescriptionInvalidException(object, "is missing prefix bytes"))); } } @Override public Row expandRow (FDBStore store, Session session, FDBStoreData storeData, Schema schema) { return FDBStoreDataHelper.expandRow(schema, storeData); } @Override public void packRow (FDBStore store, Session session, FDBStoreData storeData, Row row) { FDBStoreDataHelper.packRow(row, storeData); } /** Convert Persistit <code>Key</code> into raw key. */ public byte[] getKeyBytes(Key key) { return getKeyBytes(key, null); } public byte[] getKeyBytes(Key key, FDBStoreData.NudgeDir nudged) { byte[] keyBytes = Arrays.copyOf(key.getEncodedBytes(), key.getEncodedSize()); return Tuple2.from(keyBytes).pack(); } /** Converted decoded key <code>Tuple</code> into Persistit <code>Key</code>. */ public void getTupleKey(Tuple2 t, Key key) { assert (t.size() == 1) : t; byte[] keyBytes = t.getBytes(0); key.clear(); if(key.getMaximumSize() < keyBytes.length) { key.setMaximumSize(keyBytes.length); } System.arraycopy(keyBytes, 0, key.getEncodedBytes(), 0, keyBytes.length); key.setEncodedSize(keyBytes.length); } protected Row overlayBlobData(RowType rowType, Row row, FDBStore store, Session session) { Row result = row; if (store.isBlobReturnModeUnwrapped()) { OverlayingRow newRow = new OverlayingRow(row); for( int blobIndex = 0; blobIndex < rowType.nFields(); blobIndex ++) { if (AkBlob.isBlob(rowType.typeAt(blobIndex).typeClass())) { BlobRef oldBlob = getBlobFromRow(row.value(blobIndex)); if (oldBlob == null) { continue; } byte[] blobData = store.getBlobData(session, oldBlob); if (blobData == null) { blobData = new byte[0]; } BlobRef newBlob = new BlobRef(blobData, BlobRef.LeadingBitState.NO); newBlob.setIsReturnedBlobInUnwrappedMode(true); if (oldBlob.isLongLob()) { newBlob.setId(oldBlob.getId()); newBlob.setLobType(BlobRef.LobType.LONG_LOB); } else { newBlob.setLobType(BlobRef.LobType.SHORT_LOB); } newRow.overlay(blobIndex, newBlob); result = newRow; } } } return result; } private BlobRef getBlobFromRow(ValueSource value) { Object object = value.getObject(); if ( object == null ) { return null; } if ( object instanceof BlobRef) { BlobRef blob = (BlobRef) object; // needed for protobuf storage format if (!blob.isLongLob() && !blob.isShortLob()){ blob = new BlobRef(blob.getValue(), BlobRef.LeadingBitState.YES); } return blob; } throw new AkibanInternalException("Value must be a blob"); } /** Store contents of <code>storeData</code> into database. * Usually, key comes from <code>storeData.rawKey</code> via {@link getKeyBytes} * and value comes from <code>storeData.rawValue</code> via {@link #packRowData}. */ public void store(FDBStore store, Session session, FDBStoreData storeData) { if (storeData.persistitValue != null) { storeData.rawValue = Arrays.copyOf(storeData.persistitValue.getEncodedBytes(), storeData.persistitValue.getEncodedSize()); } store.getTransaction(session, storeData) .setBytes(storeData.rawKey, storeData.rawValue); } /** Fetch contents of database into <code>storeData</code>. * Usually, key comes from <code>storeData.rawKey</code> via {@link getKeyBytes} * and value goes into <code>storeData.rawValue</code> for {@link #expandRowData}. */ public boolean fetch(FDBStore store, Session session, FDBStoreData storeData) { storeData.rawValue = store.getTransaction(session, storeData).getValue(storeData.rawKey); return (storeData.rawValue != null); } /** Clear contents of database based on <code>storeData</code>. * Usually, key comes from <code>storeData.rawKey</code> via {@link getKeyBytes}. */ public void clear(FDBStore store, Session session, FDBStoreData storeData) { TransactionState txn = store.getTransaction(session, storeData); txn.clearKey(storeData.rawKey); } /** Set up <code>storeData.iterator</code> to iterate over group within the given * boundaries. */ public void groupIterator(FDBStore store, Session session, FDBStoreData storeData, FDBStore.GroupIteratorBoundary left, FDBStore.GroupIteratorBoundary right, int limit, FDBScanTransactionOptions transactionOptions) { TransactionState txnState = store.getTransaction(session, storeData); if ((left == FDBStore.GroupIteratorBoundary.KEY) && (right == FDBStore.GroupIteratorBoundary.NEXT_KEY) && (limit == 1)) { byte[] key = packKey(storeData); Future<byte[]> future = txnState.getFuture(key, transactionOptions); storeData.iterator = new FDBStoreDataSingleKeyValueIterator(storeData, key, future); return; } KeySelector ksLeft, ksRight; switch (left) { case START: ksLeft = KeySelector.firstGreaterOrEqual(prefixBytes(storeData)); break; case KEY: ksLeft = KeySelector.firstGreaterOrEqual(packKey(storeData)); break; case NEXT_KEY: ksLeft = KeySelector.firstGreaterThan(packKey(storeData)); break; case FIRST_DESCENDANT: ksLeft = KeySelector.firstGreaterOrEqual(packKey(storeData, Key.BEFORE)); break; default: throw new IllegalArgumentException(left.toString()); } switch (right) { case END: ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(prefixBytes(storeData))); break; case LAST_DESCENDANT: ksRight = KeySelector.firstGreaterOrEqual(packKey(storeData, Key.AFTER)); break; default: throw new IllegalArgumentException(right.toString()); } storeData.iterator = new FDBStoreDataKeyValueIterator(storeData, txnState.getRangeIterator(ksLeft, ksRight, limit, false, transactionOptions)); } /** Set up <code>storeData.iterator</code> to iterate over index. * @param key Start at <code>storeData.persistitKey</code> * @param startInclusive Include key itself in result. * @param endInclusive Include end key in result. * @param reverse Iterate in reverse. * @param snapshot Snapshot range scan */ public void indexIterator(FDBStore store, Session session, FDBStoreData storeData, boolean key, boolean startInclusive, boolean endInclusive, boolean reverse, FDBScanTransactionOptions transactionOptions) { KeySelector ksLeft, ksRight; byte[] endKey = null; if (storeData.endKey.getDepth() > 0) { endKey = packedTuple(storeData.storageDescription, storeData.endKey, null, null); } else { assert storeData.endKey.getEncodedSize() == 0; } byte[] prefixBytes = prefixBytes(storeData); if (!key) { ksLeft = KeySelector.firstGreaterOrEqual(prefixBytes); ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(prefixBytes)); } else if (startInclusive) { if (reverse) { if (endKey != null) { if (endInclusive) { ksLeft = KeySelector.firstGreaterOrEqual(endKey); } else { ksLeft = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(endKey)); } } else { ksLeft = KeySelector.firstGreaterThan(prefixBytes); } ksRight = KeySelector.firstGreaterThan(packKey(storeData)); } else { ksLeft = KeySelector.firstGreaterOrEqual(packKey(storeData)); if (endKey != null) { if (endInclusive) { ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(endKey)); } else { ksRight = KeySelector.firstGreaterOrEqual(endKey); } } else { ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(prefixBytes)); } } } else { if (reverse) { if (endKey != null) { if (endInclusive) { ksLeft = KeySelector.firstGreaterOrEqual(endKey); } else { ksLeft = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(endKey)); } } else { ksLeft = KeySelector.firstGreaterThan(prefixBytes); } ksRight = KeySelector.firstGreaterOrEqual(packKey(storeData)); } else { ksLeft = KeySelector.firstGreaterThan(packKey(storeData)); if (endKey != null) { if (endInclusive) { ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(endKey)); } else { ksRight = KeySelector.firstGreaterOrEqual(endKey); } } else { ksRight = KeySelector.firstGreaterOrEqual(ByteArrayUtil.strinc(prefixBytes)); } } } LOG.trace (" Reverse: " + reverse +", startInclusive: {}, endInclusive: {}", startInclusive, endInclusive); LOG.trace ("generated KeySelectors: Left: {}, Right: {}", ksLeft, ksRight); TransactionState txnState = store.getTransaction(session, storeData); storeData.iterator = new FDBStoreDataKeyValueIterator(storeData, txnState.getRangeIterator(ksLeft, ksRight, Transaction.ROW_LIMIT_UNLIMITED, reverse, transactionOptions)); } }