Package com.foundationdb.server.store

Source Code of com.foundationdb.server.store.FDBStore

/**
* 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.KeyValue;
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.util.TableChange.ChangeType;
import com.foundationdb.ais.util.TableChangeValidator.ChangeLevel;
import com.foundationdb.directory.DirectorySubspace;
import com.foundationdb.directory.PathUtil;
import com.foundationdb.qp.row.HKey;
import com.foundationdb.qp.row.IndexRow;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.row.WriteIndexRow;
import com.foundationdb.qp.storeadapter.FDBAdapter;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.storeadapter.indexrow.FDBIndexRow;
import com.foundationdb.qp.storeadapter.indexrow.SpatialColumnHandler;
import com.foundationdb.server.error.DuplicateKeyException;
import com.foundationdb.server.error.FDBNotCommittedException;
import com.foundationdb.server.error.QueryCanceledException;
import com.foundationdb.server.rowdata.FieldDef;
import com.foundationdb.server.rowdata.RowData;
import com.foundationdb.server.rowdata.RowDef;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.ServiceManager;
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.service.TypesRegistryService;
import com.foundationdb.server.util.ReadWriteMap;
import com.foundationdb.Range;
import com.foundationdb.Transaction;
import com.foundationdb.async.Function;
import com.foundationdb.tuple.Tuple2;
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 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 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>());
    }

    @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 rawValue = cache.nextCacheValue();
        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.currentValue();
        } 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);
    }

    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());
    }

    @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,
                                                   RowDef childRowDef,
                                                   RowData childRowData) {
        Key parentPkKey = storeData.persistitKey;
        PersistitKeyAppender keyAppender = PersistitKeyAppender.create(parentPkKey, parentPKIndex.getIndexName());
        int[] fields = childRowDef.getParentJoinFields();
        for(int field : fields) {
            FieldDef fieldDef = childRowDef.getFieldDef(field);
            keyAppender.append(fieldDef, childRowData);
        }

        // 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,
                              RowData rowData,
                              Key hKey,
                              WriteIndexRow indexRow,
                              SpatialColumnHandler spatialColumnHandler,
                              long zValue,
                              boolean doLock) {
        TransactionState txn = txnService.getTransaction(session);
        Key indexKey = createKey();
        constructIndexRow(session, indexKey, rowData, index, hKey, indexRow, spatialColumnHandler, zValue, true);
        checkUniqueness(session, txn, index, rowData, indexKey);

        byte[] packedKey = packedTuple(index, indexKey);
        txn.setBytes(packedKey, EMPTY_BYTE_ARRAY);
    }

    @Override
    public void deleteIndexRow(Session session,
                               TableIndex index,
                               RowData rowData,
                               Key hKey,
                               WriteIndexRow indexRow,
                               SpatialColumnHandler spatialColumnHandler,
                               long zValue,
                               boolean doLock) {
        TransactionState txn = txnService.getTransaction(session);
        Key indexKey = createKey();
        constructIndexRow(session, indexKey, rowData, index, hKey, indexRow, spatialColumnHandler, zValue, false);
        byte[] packed = packedTuple(index, indexKey);
        txn.clearKey(packed);
    }

    @Override
    protected void lock(Session session, FDBStoreData storeData, RowDef rowDef, RowData rowData) {
        // None
    }

    @Override
    protected void trackTableWrite(Session session, Table table) {
        // None
    }

    @Override
    protected void lock(Session session, Row row) {
        // 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 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) {
        truncateTree(session, object);
    }

    @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) {
        for (Sequence sequence : sequences) {
            session.remove(SEQ_UPDATES_KEY, SequenceCache.cacheKey(sequence));
            sequenceCache.remove(sequence.getStorageUniqueKey());
            removeIfExists(session, rootDir, FDBNameGenerator.dataPath(sequence));
        }
    }

    @Override
    public FDBAdapter createAdapter(Session session, Schema schema) {
        return new FDBAdapter(this, schema, 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()) {
                            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());
            }
        }
    }

    @Override
    public void traverse(Session session, Group group, TreeRecordVisitor visitor) {
        visitor.initialize(session, this);
        FDBStoreData storeData = createStoreData(session, group);
        groupIterator(session, storeData);
        while (storeData.next()) {
            RowData rowData = new RowData();
            expandGroupData(session, storeData, rowData);
            visitor.visit(storeData.persistitKey, rowData);
        }
    }

    public void expandGroupData(Session session, FDBStoreData storeData, RowData rowData) {
        unpackKey(storeData);
        expandRowData(session, storeData, rowData);
    }

    @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);
        long nextCommitTime = 0;
        if (scanTimeLimit >= 0) {
            nextCommitTime = txn.getStartTime() + scanTimeLimit;
        }
        indexIterator(session, storeData, false);
        while(storeData.next()) {
            // Key
            unpackKey(storeData);

            // Value
            unpackValue(storeData);

            // Visit
            visitor.visit(storeData.persistitKey, storeData.persistitValue);

            if ((scanTimeLimit >= 0) &&
                (System.currentTimeMillis() >= nextCommitTime)) {
                storeData.closeIterator();
                txn.commitAndReset(session);
                if (sleepTime > 0) {
                    try {
                        Thread.sleep(sleepTime);
                    }
                    catch (InterruptedException ex) {
                        throw new QueryCanceledException(session);
                    }
                }
                nextCommitTime = txn.getStartTime() + scanTimeLimit;
                indexIterator(session, storeData, false, false);
            }           
        }
        return visitor;
    }

    @Override
    public String getName() {
        return "FoundationDB APIv" + holder.getAPIVersion();
    }

    @Override
    public Collection<String> getStorageDescriptionNames() {
        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;
    }

    //
    // KeyCreator
    //

    @Override
    public Key createKey() {
        return new Key(null, 2047);
    }

    @Override
    public HKey newHKey(com.foundationdb.ais.model.HKey hKey) {
        throw new UnsupportedOperationException ();
    }
    //
    // 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 the whole group. */
    public void groupIterator(Session session, FDBStoreData storeData) {
        groupIterator(session, storeData,
                      GroupIteratorBoundary.START, GroupIteratorBoundary.END,
                      Transaction.ROW_LIMIT_UNLIMITED, false);
    }

    /** Resume iteration after <code>storeData.persistitKey</code>. */
    public void groupIterator(Session session, FDBStoreData storeData,
                              boolean restart, int limit) {
        groupIterator(session, storeData,
                 restart ? GroupIteratorBoundary.NEXT_KEY : GroupIteratorBoundary.START, GroupIteratorBoundary.END,
                 limit, false);
    }
   
    /** Iterate over just <code>storeData.persistitKey</code>, if present. */
    public void groupKeyIterator(Session session, FDBStoreData storeData) {
        // NOTE: Caller checks whether key returned matches.
        groupIterator(session, storeData,
                      GroupIteratorBoundary.KEY, GroupIteratorBoundary.NEXT_KEY,
                      1, false);
    }

    /** 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, false);
    }

    /** Iterate over <code>storeData.persistitKey</code>'s descendants. */
    public void groupKeyAndDescendantsIterator(Session session, FDBStoreData storeData, boolean snapshot) {
        groupIterator(session, storeData,
                      GroupIteratorBoundary.KEY, GroupIteratorBoundary.LAST_DESCENDANT,
                      Transaction.ROW_LIMIT_UNLIMITED, snapshot);
    }

    public void groupIterator(Session session, FDBStoreData storeData,
                              GroupIteratorBoundary left, GroupIteratorBoundary right,
                              int limit, boolean snapshot) {
        storeData.storageDescription.groupIterator(this, session, storeData,
                                                   left, right, limit, snapshot);
    }

    /** Iterate over the whole index. */
    public void indexIterator(Session session, FDBStoreData storeData,
                              boolean reverse) {
        indexIterator(session, storeData,
                      false, false, reverse, false);
    }

    /** Iterate starting at current key. */
    public void indexIterator(Session session, FDBStoreData storeData,
                              boolean inclusive, boolean reverse) {
        indexIterator(session, storeData,
                      true, inclusive, reverse, false);
    }

    public void indexIterator(Session session, FDBStoreData storeData,
                              boolean key, boolean inclusive, boolean reverse, boolean snapshot) {
        storeData.storageDescription.indexIterator(this, session, storeData,
                                                   key, inclusive, reverse, snapshot);
    }

    //
    // Internal
    //

    private void constructIndexRow(Session session,
                                   Key indexKey,
                                   RowData rowData,
                                   Index index,
                                   Key hKey,
                                   WriteIndexRow indexRow,
                                   SpatialColumnHandler spatialColumnHandler,
                                   long zValue,
                                   boolean forInsert) {
        indexKey.clear();
        indexRow.resetForWrite(index, indexKey);
        indexRow.initialize(rowData, hKey, spatialColumnHandler, zValue);
        indexRow.close(session, this, forInsert);
    }

    private void checkUniqueness(Session session, TransactionState txn, Index index, RowData rowData, Key key) {
        if(index.isUnique() && !hasNullIndexSegments(rowData, index)) {
            int realSize = key.getEncodedSize();
            key.setDepth(index.getKeyColumns().size());
            try {
                checkKeyDoesNotExistInIndex(session, txn, rowData, index, key);
            } finally {
                key.setEncodedSize(realSize);
            }
        }
    }

    private void checkKeyDoesNotExistInIndex(Session session, TransactionState txn, RowData rowData, 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, rowData, 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 = new SequenceCache(rawValue, sequenceCacheSize);
        session.put(SEQ_UPDATES_KEY, SequenceCache.cacheKey(s), newCache);
        return rawValue;
    }


    private static final ReadWriteMap.ValueCreator<Object, SequenceCache> SEQUENCE_CACHE_VALUE_CREATOR =
        new ReadWriteMap.ValueCreator<Object, SequenceCache>() {
            public SequenceCache createValueForKey (Object key) {
                return new SequenceCache();
            }
        };

    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.put(entry.getKey(), entry.getValue());
            }
        }
    };

    /**
     * Sequence storage, cache lifetime:
     * - Each sequence gets a directory, prefix used to store a single k/v pair
     *   - key: Allocated directory prefix
     *   - value: Largest value allocated (i.e. considered consumed) for the sequence
     * - Each SQL Layer keeps a local cache of pre-allocated values (class below, configurable size)
     * - When a transaction needs a value it looks in the local cache
     *   - If the cache is empty, read + write of current_value+cache_size is made on the sequence k/v
     *   - A session post-commit hook is scheduled to update the layer wide cache
     *   - Further values will come out of the session cache
     * - Note:
     *   - The cost of updating the cache is amortized across cache_size many allocations
     *   - As there is a single k/v, updating the cache is currently serial
     *   - The layer wide cache update is a post-commit hook so it is possible to lose blocks if
     *     one connection sneaks in past a previous completed one. This only leads to gaps, not
     *     duplication.
     */
    private static class SequenceCache {
        private long value;
        private long maxValue;

        public static Object cacheKey(Sequence s) {
            return s.getStorageUniqueKey();
        }

        public SequenceCache() {
            this(0L, 1L);
        }

        public SequenceCache(long startValue, long cacheSize) {
            this.value = startValue;
            this.maxValue = startValue + cacheSize;
        }

        public synchronized long nextCacheValue() {
            if (++value == maxValue) {
                // ensure the next call to nextCacheValue also fails
                --value;
                return -1;
            }
            return value;
        }

        public synchronized long currentValue() {
            return value;
        }

        @Override
        public String toString() {
            return String.format("SequenceCache(value=%d,maxValue=%d)", value, maxValue);
        }
    }
}
TOP

Related Classes of com.foundationdb.server.store.FDBStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.