/** * 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.columnkeys; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.HasStorage; import com.foundationdb.ais.model.StorageDescription; import com.foundationdb.ais.model.Table; 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.ais.protobuf.FDBProtobuf.ColumnKeys; import com.foundationdb.ais.protobuf.FDBProtobuf.TupleUsage; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.row.ValuesHolderRow; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.rowtype.Schema; 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.FDBTransactionService.TransactionState; import com.foundationdb.server.store.format.FDBStorageDescription; import com.foundationdb.server.store.format.tuple.TupleRowDataConverter; import com.foundationdb.server.store.format.tuple.TupleStorageDescription; import com.foundationdb.server.types.value.ValueSources; import com.foundationdb.tuple.ByteArrayUtil; import com.foundationdb.tuple.Tuple2; import com.persistit.Key; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.foundationdb.server.store.FDBStoreDataHelper.*; /** * Encode a row as separate key/value pairs, one for each column. * The key is the same as for {@link TupleStorageDescription}, plus an * element of the column name string. * The value is a single <code>Tuple</code> element. * Child rows follow immediately after the last parent column, due to * the tuple encoding using 02 for column name strings and 0C-1C for * ordinal integers. */ public class ColumnKeysStorageDescription extends FDBStorageDescription { protected static final byte[] FIRST_NUMERIC = { 0x0C }; public ColumnKeysStorageDescription(HasStorage forObject, String storageFormat) { super(forObject, storageFormat); } public ColumnKeysStorageDescription(HasStorage forObject, ColumnKeysStorageDescription other, String storageFormat) { super(forObject, other, storageFormat); } @Override public StorageDescription cloneForObject(HasStorage forObject) { return new ColumnKeysStorageDescription(forObject, this, storageFormat); } @Override public StorageDescription cloneForObjectWithoutState(HasStorage forObject) { return new ColumnKeysStorageDescription(forObject, storageFormat); } @Override public void writeProtobuf(Storage.Builder builder) { super.writeProtobuf(builder); builder.setExtension(FDBProtobuf.columnKeys, ColumnKeys.YES); // no options yet writeUnknownFields(builder); } @Override public void validate(AISValidationOutput output) { super.validate(output); if (!(object instanceof Group)) { output.reportFailure(new AISValidationFailure(new StorageDescriptionInvalidException(object, "is not a Group"))); return; } List<String> illegal = TupleRowDataConverter.checkTypes((Group)object, TupleUsage.KEY_AND_ROW); if (!illegal.isEmpty()) { output.reportFailure(new AISValidationFailure(new StorageDescriptionInvalidException(object, "has some types that cannot be stored in a Tuple: " + illegal))); } } @Override public byte[] getKeyBytes(Key key, FDBStoreData.NudgeDir nudged) { assert nudged == null : "Nudge only expected during mixed mode index iteration"; return getKeyBytes(key); } @Override public byte[] getKeyBytes(Key key ) { return TupleStorageDescription.getKeyBytesInternal(key, null); } @Override public void getTupleKey(Tuple2 t, Key key) { key.clear(); TupleStorageDescription.appendHKeySegments(t, key, ((Group)object)); } @Override public void packRow (FDBStore store, Session session, FDBStoreData storeData, Row row) { int nfields = row.rowType().nFields(); Map<String,Object> value = new HashMap<>(nfields); // Intermediate form of value. for (int i = 0; i < nfields; i++) { value.put(row.rowType().table().getColumn(i).getName(), ValueSources.toObject(row.value(i))); } storeData.otherValue = value; } @Override @SuppressWarnings("unchecked") public Row expandRow(FDBStore store, Session session, FDBStoreData storeData, Schema schema) { Map<String,Object> value = (Map<String,Object>)storeData.otherValue; Table table = TupleStorageDescription.tableFromOrdinals((Group)object, storeData.persistitKey); RowType rowType = schema.tableRowType(table); int nfields = rowType.nFields(); Object[] objects = new Object[nfields]; for (int i = 0; i < nfields; i++) { objects[i] = value.get(rowType.fieldColumn(i).getName()); } Row row = new ValuesHolderRow(rowType, objects); row = overlayBlobData(rowType, row, store, session); return row; } @Override @SuppressWarnings("unchecked") public void store(FDBStore store, Session session, FDBStoreData storeData) { TransactionState txn = store.getTransaction(session, storeData); // Erase all previous column values, in case not present in Map. txn.clearRange(storeData.rawKey, ByteArrayUtil.join(storeData.rawKey, FIRST_NUMERIC)); Map<String,Object> value = (Map<String,Object>)storeData.otherValue; for (Map.Entry<String,Object> entry : value.entrySet()) { txn.setBytes(ByteArrayUtil.join(storeData.rawKey, Tuple2.from(entry.getKey()).pack()), Tuple2.from(entry.getValue()).pack()); } } @Override public boolean fetch(FDBStore store, Session session, FDBStoreData storeData) { // Cannot get in a single fetch. try { groupIterator(store, session, storeData, FDBStore.GroupIteratorBoundary.KEY, FDBStore.GroupIteratorBoundary.FIRST_DESCENDANT, 1, FDBScanTransactionOptions.NORMAL); return storeData.next(); } finally { storeData.closeIterator(); } } @Override public void clear(FDBStore store, Session session, FDBStoreData storeData) { TransactionState txn = store.getTransaction(session, storeData); byte[] begin = storeData.rawKey; byte[] end = ByteArrayUtil.join(begin, FIRST_NUMERIC); txn.clearRange(begin, end); } @Override public void groupIterator(FDBStore store, Session session, FDBStoreData storeData, FDBStore.GroupIteratorBoundary left, FDBStore.GroupIteratorBoundary right, int limit, FDBScanTransactionOptions transactionOptions) { byte[] begin, end; switch (left) { case START: begin = prefixBytes(storeData); break; case KEY: begin = packKey(storeData); break; case NEXT_KEY: // Meaning possibly descendants. case FIRST_DESCENDANT: begin = ByteArrayUtil.join(packKey(storeData), FIRST_NUMERIC); break; default: throw new IllegalArgumentException(left.toString()); } switch (right) { case END: end = ByteArrayUtil.strinc(prefixBytes(storeData)); break; case NEXT_KEY: case FIRST_DESCENDANT: end = ByteArrayUtil.join(packKey(storeData), FIRST_NUMERIC); break; case LAST_DESCENDANT: end = packKey(storeData, Key.AFTER); break; default: throw new IllegalArgumentException(right.toString()); } storeData.iterator = new ColumnKeysStorageIterator(storeData, store.getTransaction(session, storeData) .getRangeIterator(begin, end, transactionOptions), limit); } public void indexIterator(FDBStore store, Session session, FDBStoreData storeData, boolean key, boolean inclusive, boolean reverse) { throw new UnsupportedOperationException(); } }