/**
* 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;
import com.foundationdb.*;
import com.foundationdb.async.Function;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.Group;
import com.foundationdb.ais.model.HasStorage;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.Sequence;
import com.foundationdb.ais.model.StorageDescription;
import com.foundationdb.ais.model.Table;
import com.foundationdb.ais.model.TableIndex;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.model.AbstractVisitor;
import com.foundationdb.ais.model.GroupsContainsLobsVisitor;
import com.foundationdb.ais.util.TableChange.ChangeType;
import com.foundationdb.ais.util.TableChangeValidator.ChangeLevel;
import com.foundationdb.async.AsyncIterator;
import com.foundationdb.directory.DirectorySubspace;
import com.foundationdb.directory.PathUtil;
import com.foundationdb.qp.row.IndexRow;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.WriteIndexRow;
import com.foundationdb.qp.row.OverlayingRow;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.qp.storeadapter.FDBAdapter;
import com.foundationdb.qp.storeadapter.indexrow.FDBIndexRow;
import com.foundationdb.qp.storeadapter.indexrow.SpatialColumnHandler;
import com.foundationdb.qp.util.SchemaCache;
import com.foundationdb.server.error.*;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.ServiceManager;
import com.foundationdb.server.service.blob.BlobRef;
import com.foundationdb.server.service.blob.LobService;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.server.service.listener.ListenerService;
import com.foundationdb.server.service.metrics.LongMetric;
import com.foundationdb.server.service.metrics.MetricsService;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.server.service.transaction.TransactionService;
import com.foundationdb.server.store.FDBTransactionService.TransactionState;
import com.foundationdb.server.store.TableChanges.Change;
import com.foundationdb.server.store.TableChanges.ChangeSet;
import com.foundationdb.server.store.format.FDBStorageDescription;
import com.foundationdb.server.types.aksql.aktypes.AkBlob;
import com.foundationdb.server.types.aksql.aktypes.AkGUID;
import com.foundationdb.server.types.service.TypesRegistryService;
import com.foundationdb.server.util.ReadWriteMap;
import com.foundationdb.tuple.Tuple2;
import com.foundationdb.tuple.Tuple;
import com.google.inject.Inject;
import com.persistit.Key;
import com.persistit.Persistit;
import com.persistit.Value;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import static com.foundationdb.server.store.FDBStoreDataHelper.*;
public class FDBStore extends AbstractStore<FDBStore,FDBStoreData,FDBStorageDescription> implements Service {
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final Session.MapKey<Object, SequenceCache> SEQ_UPDATES_KEY = Session.MapKey.mapNamed("SEQ_UPDATE");
private final FDBHolder holder;
private final ConfigurationService configService;
private final FDBSchemaManager schemaManager;
private final FDBTransactionService txnService;
private final MetricsService metricsService;
private final ReadWriteMap<Object, SequenceCache> sequenceCache;
private static LobService lobService;
private static final String ROWS_FETCHED_METRIC = "SQLLayerRowsFetched";
private static final String ROWS_STORED_METRIC = "SQLLayerRowsStored";
private static final String ROWS_CLEARED_METRIC = "SQLLayerRowsCleared";
private static final String CONFIG_SEQUENCE_CACHE_SIZE = "fdbsql.fdb.sequence_cache_size";
private LongMetric rowsFetchedMetric, rowsStoredMetric, rowsClearedMetric;
private DirectorySubspace rootDir;
private int sequenceCacheSize;
@Inject
public FDBStore(FDBHolder holder,
ConfigurationService configService,
SchemaManager schemaManager,
TransactionService txnService,
ListenerService listenerService,
TypesRegistryService typesRegistryService,
ServiceManager serviceManager,
MetricsService metricsService) {
super(txnService, schemaManager, listenerService, typesRegistryService, serviceManager);
this.holder = holder;
this.configService = configService;
if(schemaManager instanceof FDBSchemaManager) {
this.schemaManager = (FDBSchemaManager)schemaManager;
} else {
throw new IllegalStateException("Only usable with FDBSchemaManager, found: " + txnService);
}
if(txnService instanceof FDBTransactionService) {
this.txnService = (FDBTransactionService)txnService;
} else {
throw new IllegalStateException("Only usable with FDBTransactionService, found: " + txnService);
}
this.metricsService = metricsService;
this.sequenceCache = ReadWriteMap.wrapFair(new HashMap<Object, SequenceCache>());
lobService = serviceManager.getServiceByClass(LobService.class);
}
@Override
public long nextSequenceValue(Session session, Sequence sequence) {
Map<Object, SequenceCache> sessionMap = session.get(SEQ_UPDATES_KEY);
SequenceCache cache = null;
if(sessionMap != null) {
cache = sessionMap.get(SequenceCache.cacheKey(sequence));
}
if(cache == null) {
cache = sequenceCache.getOrCreateAndPut(SequenceCache.cacheKey(sequence), SEQUENCE_CACHE_VALUE_CREATOR);
long readTimestamp = txnService.getTransactionStartTimestamp(session);
if(readTimestamp < cache.getTimestamp()) {
cache = null;
}
}
long rawValue = (cache != null) ? cache.nextCacheValue() : -1;
if(rawValue < 0) {
rawValue = updateSequenceCache(session, sequence);
}
return sequence.realValueForRawNumber(rawValue);
}
@Override
public long curSequenceValue(Session session, Sequence sequence) {
long rawValue = 0;
SequenceCache cache = sequenceCache.get(sequence.getStorageUniqueKey());
if(cache == null) {
cache = session.get(SEQ_UPDATES_KEY, sequence.getStorageUniqueKey());
}
if (cache != null) {
rawValue = cache.getCurrentValue();
} else {
// TODO: Allow FDBStorageDescription to intervene?
TransactionState txn = txnService.getTransaction(session);
byte[] byteValue = txn.getValue(prefixBytes(sequence));
if(byteValue != null) {
Tuple2 tuple = Tuple2.fromBytes(byteValue);
rawValue = tuple.getLong(0);
}
}
return sequence.realValueForRawNumber(rawValue);
}
@Override
public void dropSchema(Session session, com.foundationdb.ais.model.Schema schema) {
super.dropSchema(session, schema);
removeFromCache(session, schema.getSequences().values());
}
public void setRollbackPending(Session session) {
if(txnService.isTransactionActive(session)) {
txnService.setRollbackPending(session);
}
}
//
// Service
//
@Override
public void start() {
rowsFetchedMetric = metricsService.addLongMetric(ROWS_FETCHED_METRIC);
rowsStoredMetric = metricsService.addLongMetric(ROWS_STORED_METRIC);
rowsClearedMetric = metricsService.addLongMetric(ROWS_CLEARED_METRIC);
rootDir = holder.getRootDirectory();
boolean withConcurrentDML = Boolean.parseBoolean(configService.getProperty(FEATURE_DDL_WITH_DML_PROP));
this.sequenceCacheSize = Integer.parseInt(configService.getProperty(CONFIG_SEQUENCE_CACHE_SIZE));
this.constraintHandler = new FDBConstraintHandler(this, configService, typesRegistryService, serviceManager, txnService);
this.onlineHelper = new OnlineHelper(txnService, schemaManager, this, typesRegistryService, constraintHandler, withConcurrentDML);
listenerService.registerRowListener(onlineHelper);
}
@Override
public void stop() {
}
@Override
public void crash() {
}
//
// Store
//
@Override
public FDBStoreData createStoreData(Session session, FDBStorageDescription storageDescription) {
return new FDBStoreData(session, storageDescription, createKey(), createKey());
}
@Override
protected void releaseStoreData(Session session, FDBStoreData storeData) {
// None
}
@Override
FDBStorageDescription getStorageDescription(FDBStoreData storeData) {
return storeData.storageDescription;
}
@Override
protected Key getKey(Session session, FDBStoreData storeData) {
return storeData.persistitKey;
}
@Override
protected void store(Session session, FDBStoreData storeData) {
packKey(storeData);
storeData.storageDescription.store(this, session, storeData);
rowsStoredMetric.increment();
}
@Override
protected boolean fetch(Session session, FDBStoreData storeData) {
packKey(storeData);
boolean result = storeData.storageDescription.fetch(this, session, storeData);
rowsFetchedMetric.increment();
return result;
}
@Override
protected void clear(Session session, FDBStoreData storeData) {
packKey(storeData);
storeData.storageDescription.clear(this, session, storeData);
rowsClearedMetric.increment();
}
@Override
void resetForWrite(FDBStoreData storeData, Index index, WriteIndexRow indexRowBuffer) {
if(storeData.persistitValue == null) {
storeData.persistitValue = new Value((Persistit) null);
}
indexRowBuffer.resetForWrite(index, storeData.persistitKey, storeData.persistitValue);
}
@Override
protected Iterator<Void> createDescendantIterator(Session session, final FDBStoreData storeData) {
groupDescendantsIterator(session, storeData);
return new Iterator<Void>() {
@Override
public boolean hasNext() {
return storeData.iterator.hasNext();
}
@Override
public Void next() {
storeData.iterator.next();
unpackKey(storeData);
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
protected IndexRow readIndexRow(Session session, Index parentPKIndex, FDBStoreData storeData, Row childRow) {
Key parentPkKey = storeData.persistitKey;
PersistitKeyAppender keyAppender = PersistitKeyAppender.create(parentPkKey, parentPKIndex.getIndexName());
for (Column column : childRow.rowType().table().getParentJoin().getChildColumns()) {
keyAppender.append(childRow.value(column.getPosition()), column);
}
// Only called when child row does not contain full HKey.
// Key contents are the logical parent of the actual index entry (if it exists).
byte[] packed = packedTuple(parentPKIndex, parentPkKey);
byte[] end = packedTuple(parentPKIndex, parentPkKey, Key.AFTER);
TransactionState txn = txnService.getTransaction(session);
List<KeyValue> pkValue = txn.getRangeAsValueList(packed, end);
FDBIndexRow indexRow = null;
if (!pkValue.isEmpty()) {
assert pkValue.size() == 1 : parentPKIndex;
KeyValue kv = pkValue.get(0);
assert kv.getValue().length == 0 : parentPKIndex + ", " + kv;
indexRow = new FDBIndexRow(this);
FDBStoreDataHelper.unpackTuple(parentPKIndex, parentPkKey, kv.getKey());
indexRow.resetForRead(parentPKIndex, parentPkKey, null);
}
return indexRow;
}
@Override
public void writeIndexRow(Session session,
TableIndex index,
Row row,
Key hKey,
WriteIndexRow indexRow,
SpatialColumnHandler spatialColumnHandler,
long zValue,
boolean doLock) {
TransactionState txn = txnService.getTransaction(session);
Key indexKey = createKey();
constructIndexRow(session, indexKey, row, index, hKey, indexRow, spatialColumnHandler, zValue, true);
checkUniqueness(session, txn, index, row, indexKey);
byte[] packedKey = packedTuple(index, indexKey);
txn.setBytes(packedKey, EMPTY_BYTE_ARRAY);
}
@Override
public void deleteIndexRow(Session session, TableIndex index, Row row, Key hKey, WriteIndexRow indexRow,
SpatialColumnHandler spatialColumnHandler, long zValue, boolean doLock) {
TransactionState txn = txnService.getTransaction(session);
Key indexKey = createKey();
constructIndexRow(session, indexKey, row, index, hKey, indexRow, spatialColumnHandler, zValue, false);
byte[] packed = packedTuple(index, indexKey);
txn.clearKey(packed);
}
@Override
protected void lock (Session session, FDBStoreData storeData, Row row) {
// None
}
@Override
protected void lock(Session session, Row row) {
// None
}
@Override
protected void trackTableWrite(Session session, Table table) {
// None
}
@Override
public void truncateTree(Session session, HasStorage object) {
TransactionState txn = txnService.getTransaction(session);
txn.clearRange(Range.startsWith(prefixBytes(object)));
}
@Override
public void deleteIndexes(Session session, Collection<? extends Index> indexes) {
for(Index index : indexes) {
removeIfExists(session, rootDir, FDBNameGenerator.dataPath(index));
}
}
@Override
public void dropGroup(final Session session, Group group) {
deleteLobs(session, group);
group.getRoot().visit(new AbstractVisitor() {
@Override
public void visit(Table table) {
removeTrees(session, table);
}
});
}
@Override
public void removeTrees(Session session, Table table) {
// Table and indexes (and group and group indexes if root table)
removeIfExists(session, rootDir, FDBNameGenerator.dataPath(table.getName()));
// Sequence
if(table.getIdentityColumn() != null) {
deleteSequences(session, Collections.singleton(table.getIdentityColumn().getIdentityGenerator()));
}
}
@Override
public void removeTrees(Session session, com.foundationdb.ais.model.Schema schema) {
removeIfExists(session, rootDir, FDBNameGenerator.dataPathSchemaTable(schema.getName()));
removeIfExists(session, rootDir, FDBNameGenerator.dataPathSchemaSequence(schema.getName()));
}
@Override
public void removeTree(Session session, HasStorage object) {
deleteLobs(session, object);
truncateTree(session, object);
}
private void deleteLobs(Session session, HasStorage object) {
if (object instanceof com.foundationdb.ais.model.Group) {
Group group = (Group)object;
GroupsContainsLobsVisitor visitor = new GroupsContainsLobsVisitor();
group.visit(visitor);
if (visitor.containsLob()) {
deleteLobsChecked(session, group);
}
}
}
private void deleteLobsChecked(Session session, Group group) {
FDBStoreData storeData = createStoreData(session, group);
groupIterator(session, storeData, FDBScanTransactionOptions.NORMAL);
while (storeData.next()) {
Row row = expandGroupData(session, storeData, SchemaCache.globalSchema(group.getAIS()));
deleteLobs(session, row);
}
}
@Override
public void dropAllLobs(Session session) {
lobService.clearAllLobs(session);
}
@Override
public void truncateIndexes(Session session, Collection<? extends Index> indexes) {
for(Index index : indexes) {
truncateTree(session, index);
}
}
@Override
public void deleteSequences(Session session, Collection<? extends Sequence> sequences) {
removeFromCache(session, sequences);
for (Sequence sequence : sequences) {
removeIfExists(session, rootDir, FDBNameGenerator.dataPath(sequence));
}
}
@Override
public FDBAdapter createAdapter(Session session) {
return new FDBAdapter(this, session, txnService, configService);
}
@Override
public boolean treeExists(Session session, StorageDescription storageDescription) {
TransactionState txn = txnService.getTransaction(session);
return txn.getRangeExists(Range.startsWith(prefixBytes((FDBStorageDescription) storageDescription)), 1);
}
@Override
public void discardOnlineChange(Session session, Collection<ChangeSet> changeSets) {
for(ChangeSet cs : changeSets) {
TableName newName = new TableName(cs.getNewSchema(), cs.getNewName());
removeIfExists(session, rootDir, FDBNameGenerator.onlinePath(newName));
for(Change c : cs.getIdentityChangeList()) {
switch(ChangeType.valueOf(c.getChangeType())) {
case ADD:
removeIfExists(session, rootDir, FDBNameGenerator.onlinePathSequence(newName.getSchemaName(), c.getNewName()));
break;
case DROP:
// None
break;
default:
throw new IllegalStateException(c.getChangeType());
}
}
}
}
@Override
public void finishOnlineChange(Session session, Collection<ChangeSet> changeSets) {
TransactionState txnState = txnService.getTransaction(session);
Transaction txn = txnState.getTransaction();
for(ChangeSet cs : changeSets) {
TableName oldName = new TableName(cs.getOldSchema(), cs.getOldName());
TableName newName = new TableName(cs.getNewSchema(), cs.getNewName());
for(Change c : cs.getIdentityChangeList()) {
List<String> seqOldDataPath = FDBNameGenerator.dataPathSequence(oldName.getSchemaName(), c.getOldName());
List<String> seqNewDataPath = FDBNameGenerator.dataPathSequence(newName.getSchemaName(), c.getNewName());
List<String> seqOnlinePath = FDBNameGenerator.onlinePathSequence(newName.getSchemaName(), c.getNewName());
switch(ChangeType.valueOf(c.getChangeType())) {
case ADD:
try {
rootDir.removeIfExists(txn, seqOldDataPath).get();
// Due to schema currently being create on demand
rootDir.createOrOpen(txn, PathUtil.popBack(seqNewDataPath)).get();
rootDir.move(txn, seqOnlinePath, seqNewDataPath).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(session, e);
}
break;
case DROP:
try {
rootDir.removeIfExists(txn, seqOldDataPath).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(session, e);
}
break;
default:
throw new IllegalStateException(cs.getChangeLevel());
}
}
List<String> dataPath = FDBNameGenerator.dataPath(oldName);
List<String> onlinePath = FDBNameGenerator.onlinePath(newName);
// - move renamed directories
if(!oldName.equals(newName)) {
schemaManager.renamingTable(session, oldName, newName);
dataPath = FDBNameGenerator.dataPath(newName);
}
if (!directoryExists(txnState, rootDir, onlinePath)) {
continue;
}
switch(ChangeLevel.valueOf(cs.getChangeLevel())) {
case NONE:
// None
break;
case METADATA:
case METADATA_CONSTRAINT:
case INDEX:
case INDEX_CONSTRAINT:
// - Move everything from dataOnline/foo/ to data/foo/
// - remove dataOnline/foo/
try {
for(String subPath : rootDir.list(txn, onlinePath).get()) {
List<String> subDataPath = PathUtil.extend(dataPath, subPath);
List<String> subOnlinePath = PathUtil.extend(onlinePath, subPath);
rootDir.removeIfExists(txn, subDataPath).get();
rootDir.move(txn, subOnlinePath, subDataPath).get();
}
rootDir.remove(txn, onlinePath).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(session, e);
}
break;
case TABLE:
case GROUP:
// - move unaffected from data/foo/ to dataOnline/foo/
// - remove data/foo
// - move dataOnline/foo to data/foo/
try {
if (rootDir.exists(txn, dataPath).get()) {
executeLobOnlineDelete(session, oldName);
for(String subPath : rootDir.list(txn, dataPath).get()) {
List<String> subDataPath = PathUtil.extend(dataPath, subPath);
List<String> subOnlinePath = PathUtil.extend(onlinePath, subPath);
if(!rootDir.exists(txn, subOnlinePath).get()) {
rootDir.move(txn, subDataPath, subOnlinePath).get();
}
}
rootDir.remove(txn, dataPath).get();
}
rootDir.move(txn, onlinePath, dataPath).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(session, e);
}
break;
default:
throw new IllegalStateException(cs.getChangeLevel());
}
}
}
// Test only traversal.
@Override
public void traverse(Session session, Group group, TreeRecordVisitor visitor) {
visitor.initialize(session, this);
FDBStoreData storeData = createStoreData(session, group);
groupIterator(session, storeData, FDBScanTransactionOptions.NORMAL);
while (storeData.next()) {
Row row = expandGroupData(session, storeData, SchemaCache.globalSchema(group.getAIS()));
visitor.visit(storeData.persistitKey, row);
}
}
public Row expandGroupData(Session session, FDBStoreData storeData, Schema schema) {
unpackKey(storeData);
return expandRow(session, storeData, schema);
}
// Test only Traversal
@Override
public <V extends IndexVisitor<Key, Value>> V traverse(Session session, Index index, V visitor, long scanTimeLimit, long sleepTime) {
FDBStoreData storeData = createStoreData(session, index);
storeData.persistitValue = new Value((Persistit)null);
TransactionState txn = txnService.getTransaction(session);
FDBScanTransactionOptions transactionOptions;
if (scanTimeLimit > 0) {
transactionOptions = new FDBScanTransactionOptions(true, -1,
scanTimeLimit, sleepTime);
}
else {
transactionOptions = FDBScanTransactionOptions.SNAPSHOT;
}
indexIterator(session, storeData, false, false, true, false, transactionOptions);
while(storeData.next()) {
// Key
unpackKey(storeData);
// Value
unpackValue(storeData);
// Visit
visitor.visit(storeData.persistitKey, storeData.persistitValue);
}
return visitor;
}
@Override
public String getName() {
return "FoundationDB APIv" + holder.getAPIVersion();
}
@Override
public Collection<String> getStorageDescriptionNames(Session session) {
final List<List<String>> dataDirs = Arrays.asList(
Arrays.asList(FDBNameGenerator.DATA_PATH_NAME, FDBNameGenerator.TABLE_PATH_NAME),
Arrays.asList(FDBNameGenerator.DATA_PATH_NAME, FDBNameGenerator.SEQUENCE_PATH_NAME),
Arrays.asList(FDBNameGenerator.ONLINE_PATH_NAME, FDBNameGenerator.TABLE_PATH_NAME),
Arrays.asList(FDBNameGenerator.ONLINE_PATH_NAME, FDBNameGenerator.SEQUENCE_PATH_NAME)
);
return txnService.runTransaction(new Function<Transaction, Collection<String>>() {
@Override
public Collection<String> apply(Transaction txn) {
Set<String> pathSet = new TreeSet<>();
for(List<String> dataPath : dataDirs) {
if(rootDir.exists(txn, dataPath).get()) {
for(String schemaName : rootDir.list(txn, dataPath).get()) {
List<String> schemaPath = PathUtil.extend(dataPath, schemaName);
for(String o : rootDir.list(txn, schemaPath).get()) {
pathSet.add(PathUtil.extend(schemaPath, o).toString());
}
}
}
}
return pathSet;
}
});
}
@Override
public Class<? extends Exception> getOnlineDMLFailureException() {
return FDBNotCommittedException.class;
}
@Override
protected Row storeLobs(Session session, Row row) {
RowType rowType = row.rowType();
OverlayingRow resRow = new OverlayingRow(row);
Boolean changedRow = false;
for ( int i = 0; i < rowType.nFields(); i++ ) {
if (AkBlob.isBlob(rowType.typeAt(i).typeClass())) {
int tableId = rowType.table().getTableId();
BlobRef blobRefInit = (BlobRef)row.value(i).getObject();
if (blobRefInit == null) {
continue;
}
BlobRef.LeadingBitState state;
BlobRef.LobType type;
byte[] value = blobRefInit.getValue();
// The contract:
// if blobReturnMode == UNWRAPPED
// - all BlobRef value contains no leading bit
// - data is always the data to be stored, never GUID of the blob
// - all output to users is data only
// if blobReturnMode == WRAPPED
// - all input contains a leading bit
// - if leading bit == LONG_BLOB
// data is GUID of the blob
// else data is content of the blob (which can be long or short blob)
//
// all output of this function contains a leading bit of a type depending on the storage
//
//
if (isBlobReturnModeUnwrapped()) {
state = BlobRef.LeadingBitState.NO;
} else {
state = BlobRef.LeadingBitState.YES;
}
// ensure correct state of the leading bit
BlobRef blobRefTmp = new BlobRef(value, state, blobRefInit.getLobType(), blobRefInit.getRequestedLobType());
if (!isBlobReturnModeUnwrapped()) {
if (blobRefTmp.isLongLob()) {
type = BlobRef.LobType.LONG_LOB;
}
else if (blobRefTmp.isShortLob()) {
if (blobRefTmp.getBytes().length >= AkBlob.LOB_SWITCH_SIZE) {
UUID id = writeDataToNewBlob(session, blobRefTmp.getBytes());
value = updateValue(id);
type = BlobRef.LobType.LONG_LOB;
} else {
type = BlobRef.LobType.SHORT_LOB;
}
} else {
throw new AkibanInternalException("Unexpected state");
}
} else { // only in UNWRAPPED mode, value needs updating, adding leading bit and correct value content (guid)
// first verify if specific requested format is allowed --> should be done in functions.
if (blobRefTmp.getRequestedLobType() == BlobRef.LobType.LONG_LOB ||
blobRefTmp.getBytes().length >= AkBlob.LOB_SWITCH_SIZE) {
if (blobRefInit.isReturnedBlobInUnwrappedMode()){
type = BlobRef.LobType.LONG_LOB;
value = updateValue(blobRefInit.getId());
} else {
UUID id = writeDataToNewBlob(session, blobRefTmp.getBytes());
type = BlobRef.LobType.LONG_LOB;
value = updateValue(id);
}
}
else {
type = BlobRef.LobType.SHORT_LOB;
value = updateValue(blobRefTmp.getBytes());
}
}
BlobRef blobRef = new BlobRef(value, BlobRef.LeadingBitState.YES, type, BlobRef.LobType.UNKNOWN);
if (blobRef.isLongLob()) {
lobService.linkTableBlob(session, blobRef.getId(), tableId);
}
resRow.overlay(i, blobRef);
changedRow = true;
}
}
return changedRow ? resRow : row;
}
public static UUID writeDataToNewBlob(Session session, byte[] data) {
UUID id = UUID.randomUUID();
lobService.createNewLob(session, id);
lobService.writeBlob(session, id, 0, data);
return id;
}
private byte[] updateValue(UUID id) {
byte[] res = new byte[17];
System.arraycopy(AkGUID.uuidToBytes(id), 0, res, 1, 16);
res[0] = BlobRef.LONG_LOB;
return res;
}
private byte[] updateValue(byte[] data) {
byte[] res = new byte[data.length+1];
System.arraycopy(data, 0, res, 1, data.length);
res[0] = BlobRef.SHORT_LOB;
return res;
}
public byte[] getBlobData(Session session, BlobRef blob) {
if (blob.isShortLob()) {
return blob.getBytes();
} else if (blob.isLongLob()) {
return lobService.readBlob(session, blob.getId());
} else {
throw new LobContentException("Type of lob not available");
}
}
public boolean isBlobReturnModeUnwrapped() {
return configService.getProperty(AkBlob.RETURN_UNWRAPPED).equalsIgnoreCase(AkBlob.UNWRAPPED) ? true : false;
}
@Override
protected void deleteLobs(Session session, Row row) {
RowType rowType = row.rowType();
for( int i = 0; i < rowType.nFields(); i++ ) {
if (AkBlob.isBlob(rowType.typeAt(i).typeClass())) {
BlobRef blobRef = (BlobRef)row.value(i).getObject();
if (blobRef == null) {
continue;
}
if (blobRef.isLongLob()) {
lobService.deleteLob(session, blobRef.getId());
}
}
}
}
@Override
protected void registerLobForOnlineDelete(Session session, TableName tableRootName, UUID lobId) {
List<String> path = FDBNameGenerator.onlineLobPath(tableRootName.getSchemaName(), tableRootName.getTableName());
TransactionState tr = txnService.getTransaction(session);
DirectorySubspace dir = rootDir.createOrOpen(tr.getTransaction(), path).get();
byte[] key = dir.pack(AkGUID.uuidToBytes(lobId));
tr.setBytes(key, EMPTY_BYTE_ARRAY);
}
@Override
protected void executeLobOnlineDelete(Session session, TableName table) {
List<String> onlineLobPath = FDBNameGenerator.onlineLobPath(table.getSchemaName(), table.getTableName());
TransactionState tr = txnService.getTransaction(session);
if (rootDir.exists(tr.getTransaction(), onlineLobPath).get()) {
DirectorySubspace dir = rootDir.createOrOpen(tr.getTransaction(), onlineLobPath).get();
AsyncIterator<KeyValue> it = tr.getRangeIterator(dir.range()).iterator();
while(it.hasNext()) {
KeyValue kv = it.next();
Tuple t = dir.unpack(kv.getKey());
lobService.deleteLob(session, AkGUID.bytesToUUID(t.getBytes(0), 0));
}
rootDir.removeIfExists(tr.getTransaction(), onlineLobPath).get();
}
}
@Override
public boolean isRestartable() {
return true;
}
//
// KeyCreator
//
@Override
public Key createKey() {
return new Key(null, 2047);
}
//
// Storage iterators
//
public TransactionState getTransaction(Session session, FDBStoreData storeData) {
return txnService.getTransaction(session);
}
public enum GroupIteratorBoundary {
START, END, KEY, NEXT_KEY,
FIRST_DESCENDANT, LAST_DESCENDANT
}
/** Iterate over just <code>storeData.persistitKey</code>, if present. */
public void groupKeyIterator(Session session, FDBStoreData storeData, FDBScanTransactionOptions transactionOptions) {
// NOTE: Caller checks whether key returned matches.
groupIterator(session, storeData,
GroupIteratorBoundary.KEY, GroupIteratorBoundary.NEXT_KEY,
1, transactionOptions);
}
/** Iterate over <code>storeData.persistitKey</code>'s descendants. */
public void groupDescendantsIterator(Session session, FDBStoreData storeData) {
groupIterator(session, storeData,
GroupIteratorBoundary.FIRST_DESCENDANT, GroupIteratorBoundary.LAST_DESCENDANT,
Transaction.ROW_LIMIT_UNLIMITED, FDBScanTransactionOptions.NORMAL);
}
/** Iterate over <code>storeData.persistitKey</code>'s descendants. */
public void groupKeyAndDescendantsIterator(Session session, FDBStoreData storeData, FDBScanTransactionOptions transactionOptions) {
groupIterator(session, storeData,
GroupIteratorBoundary.KEY, GroupIteratorBoundary.LAST_DESCENDANT,
Transaction.ROW_LIMIT_UNLIMITED, transactionOptions);
}
public void groupIterator(Session session, FDBStoreData storeData, FDBScanTransactionOptions transactionOptions) {
groupIterator(session, storeData,
GroupIteratorBoundary.START, GroupIteratorBoundary.END,
Transaction.ROW_LIMIT_UNLIMITED, transactionOptions);
}
public void groupIterator(Session session, FDBStoreData storeData,
GroupIteratorBoundary left, GroupIteratorBoundary right,
int limit, FDBScanTransactionOptions transactionOptions) {
storeData.storageDescription.groupIterator(this, session, storeData,
left, right, limit,
transactionOptions);
}
/** Iterate over the whole index. */
public void indexIterator(Session session, FDBStoreData storeData,
FDBScanTransactionOptions transactionOptions) {
indexIterator(session, storeData, false, false, true, false, transactionOptions);
}
public void indexIterator(Session session, FDBStoreData storeData,
boolean key, boolean startInclusive, boolean endInclusive, boolean reverse,
FDBScanTransactionOptions transactionOptions) {
storeData.storageDescription.indexIterator(this, session, storeData,
key, startInclusive, endInclusive, reverse,
transactionOptions);
}
//
// Internal
//
private void constructIndexRow(Session session,
Key indexKey,
Row row,
Index index,
Key hKey,
WriteIndexRow indexRow,
SpatialColumnHandler spatialColumnHandler,
long zValue,
boolean forInsert) {
indexKey.clear();
indexRow.resetForWrite(index, indexKey);
indexRow.initialize(row, hKey, spatialColumnHandler, zValue);
indexRow.close(session, forInsert);
}
private void checkUniqueness(Session session, TransactionState txn, Index index, Row row, Key key) {
if(index.isUnique() && !hasNullIndexSegments(row, index)) {
int realSize = key.getEncodedSize();
key.setDepth(index.getKeyColumns().size());
try {
checkKeyDoesNotExistInIndex(session, txn, row, index, key);
} finally {
key.setEncodedSize(realSize);
}
}
}
private void checkKeyDoesNotExistInIndex(Session session, TransactionState txn, Row row, Index index, Key key) {
assert index.isUnique() : index;
FDBPendingIndexChecks.PendingCheck<?> check =
FDBPendingIndexChecks.keyDoesNotExistInIndexCheck(session, txn, index, key);
if (txn.getForceImmediateForeignKeyCheck() ||
txn.getIndexChecks(false) == null) {
check.blockUntilReady(txn);
if (!check.check(session, txn, index)) {
// Using RowData, can give better error than check.throwException().
String msg = formatIndexRowString(session, row, index);
throw new DuplicateKeyException(index.getIndexName(), msg);
}
}
else {
txn.getIndexChecks(false).add(session, txn, index, check);
}
}
private void removeIfExists(Session session, DirectorySubspace dir, List<String> dirs) {
try {
Transaction txn = txnService.getTransaction(session).getTransaction();
dir.removeIfExists(txn, dirs).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(session, e);
}
}
private boolean directoryExists (TransactionState txn, DirectorySubspace dir, List<String> dirs) {
try {
return dir.exists(txn.getTransaction(), dirs).get();
} catch (RuntimeException e) {
throw FDBAdapter.wrapFDBException(txn.session, e);
}
}
private long updateSequenceCache(Session session, Sequence s) {
Transaction tr = txnService.getTransaction(session).getTransaction();
byte[] prefixBytes = prefixBytes(s);
byte[] byteValue = tr.get(prefixBytes).get();
final long rawValue;
if(byteValue != null) {
Tuple2 tuple = Tuple2.fromBytes(byteValue);
rawValue = tuple.getLong(0);
} else {
rawValue = 1;
}
tr.set(prefixBytes, Tuple2.from(rawValue + sequenceCacheSize).pack());
Map<Object, SequenceCache> sessionMap = session.get(SEQ_UPDATES_KEY);
if(sessionMap == null) {
txnService.addCallback(session, TransactionService.CallbackType.COMMIT, SEQUENCE_UPDATES_PUT_CALLBACK);
txnService.addCallback(session, TransactionService.CallbackType.END, SEQUENCE_UPDATES_CLEAR_CALLBACK);
}
SequenceCache newCache = SequenceCache.newLocal(rawValue, sequenceCacheSize);
session.put(SEQ_UPDATES_KEY, SequenceCache.cacheKey(s), newCache);
return rawValue;
}
private void removeFromCache(Session session, Collection<? extends Sequence> sequences) {
for(Sequence s : sequences) {
session.remove(SEQ_UPDATES_KEY, SequenceCache.cacheKey(s));
sequenceCache.remove(s.getStorageUniqueKey());
}
}
//
// Test only
//
int getSequenceCacheMapSize() {
return sequenceCache.size();
}
//
// Internal
//
private static final ReadWriteMap.ValueCreator<Object, SequenceCache> SEQUENCE_CACHE_VALUE_CREATOR =
new ReadWriteMap.ValueCreator<Object, SequenceCache>() {
public SequenceCache createValueForKey (Object key) {
return SequenceCache.newEmpty();
}
};
private static final TransactionService.Callback SEQUENCE_UPDATES_CLEAR_CALLBACK = new TransactionService.Callback() {
@Override
public void run(Session session, long timestamp) {
session.remove(SEQ_UPDATES_KEY);
}
};
private final TransactionService.Callback SEQUENCE_UPDATES_PUT_CALLBACK = new TransactionService.Callback() {
@Override
public void run(Session session, long timestamp) {
Map<Object, SequenceCache> map = session.get(SEQ_UPDATES_KEY);
for(Entry<Object, SequenceCache> entry : map.entrySet()) {
SequenceCache global = SequenceCache.newGlobal(timestamp, entry.getValue());
sequenceCache.put(entry.getKey(), global);
}
}
};
}