Java tutorial
/** * Copyright 2010 - 2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.exodus.entitystore; import jetbrains.exodus.*; import jetbrains.exodus.bindings.ComparableBinding; import jetbrains.exodus.bindings.IntegerBinding; import jetbrains.exodus.bindings.LongBinding; import jetbrains.exodus.core.dataStructures.ConcurrentObjectCache; import jetbrains.exodus.core.dataStructures.Pair; import jetbrains.exodus.core.dataStructures.hash.HashMap; import jetbrains.exodus.core.dataStructures.hash.HashSet; import jetbrains.exodus.core.dataStructures.hash.IntHashMap; import jetbrains.exodus.core.dataStructures.hash.LinkedHashMap; import jetbrains.exodus.entitystore.iterate.*; import jetbrains.exodus.entitystore.management.EntityStoreConfig; import jetbrains.exodus.entitystore.metadata.Index; import jetbrains.exodus.entitystore.metadata.IndexField; import jetbrains.exodus.entitystore.tables.*; import jetbrains.exodus.env.*; import jetbrains.exodus.log.iterate.CompoundByteIterable; import jetbrains.exodus.log.iterate.CompressedUnsignedLongByteIterable; import jetbrains.exodus.log.iterate.FixedLengthByteIterable; import jetbrains.exodus.util.ByteArraySizedInputStream; import jetbrains.exodus.util.LightByteArrayOutputStream; import jetbrains.exodus.util.UTFUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @SuppressWarnings({ "SynchronizationOnLocalVariableOrMethodParameter", "UnusedDeclaration", "ThisEscapedInObjectConstruction", "VolatileLongOrDoubleField", "ObjectAllocationInLoop", "ReuseOfLocalVariable", "rawtypes" }) public class PersistentEntityStoreImpl implements PersistentEntityStore, FlushLog.Member { private static final Log log = LogFactory.getLog(PersistentEntityStoreImpl.class); @NonNls static final String BLOBS_DIR = "blobs"; @NonNls static final String BLOBS_EXTENSION = ".blob"; @NonNls static final String BLOB_HANDLES_SEQUENCE = "blob.handles.sequence"; @NonNls private static final String SEQUENCES_STORE = "sequences"; private static final long EMPTY_BLOB_HANDLE = Long.MAX_VALUE; private static final long IN_PLACE_BLOB_HANDLE = EMPTY_BLOB_HANDLE - 1; private static final int ENTITY_ID_CACHE_SIZE = 2047; @NotNull private static final ByteArrayInputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(new byte[0]); @NotNull private static final String EMPTY_STRING = ""; private final int hashCode; @NotNull private final PersistentEntityStoreConfig config; @NotNull private final String name; @NotNull private final Environment environment; @NotNull private final String location; @NotNull private final Map<Thread, Deque<PersistentStoreTransaction>> txns = new ConcurrentHashMap<>(4, 0.75f, 4); @NotNull private final StoreNamingRules namingRulez; @NotNull private BlobVault blobVault; @NotNull private final Map<String, PersistentSequence> allSequences; @NotNull private final IntHashMap<PersistentSequence> entitiesSequences; @NotNull private final PersistentSequentialDictionary entityTypes; @NotNull private final PersistentSequentialDictionary propertyIds; @NotNull private final PersistentSequentialDictionary linkIds; @NotNull private final PropertyTypes propertyTypes; @NotNull private final PersistentSequentialDictionary propertyCustomTypeIds; @NotNull private final OpenTablesCache entitiesTables; @NotNull private final OpenTablesCache propertiesTables; @NotNull private final OpenTablesCache linksTables; @NotNull private final OpenTablesCache blobsTables; @NotNull private final OpenTablesCache entitiesHistoryTables; @NotNull private final OpenTablesCache propertiesHistoryTables; @NotNull private final OpenTablesCache linksHistoryTables; @NotNull private final OpenTablesCache blobsHistoryTables; @NotNull private final Store internalSettings; @NotNull private Store sequences; @NotNull private final EntityIterableCacheImpl iterableCache; @NotNull private final ConcurrentObjectCache<String, EntityId> entityIdCache; // this cache doesn't need snapshot isolation private Explainer explainer; private final DataGetter propertyDataGetter; private final DataGetter linkDataGetter; private final DataGetter blobDataGetter; @Nullable private final EntityStoreConfig configMBean; private final long startedAt; private long transactionCount; @NotNull private final Set<TableCreationOperation> tableCreationLog = new HashSet<>(); public PersistentEntityStoreImpl(@NotNull final Environment environment, @NotNull final String name) throws Exception { this(environment, null, name); } public PersistentEntityStoreImpl(@NotNull final Environment environment, @Nullable final BlobVault blobVault, @NotNull final String name) { this(new PersistentEntityStoreConfig(), environment, blobVault, name); } public PersistentEntityStoreImpl(@NotNull final PersistentEntityStoreConfig config, @NotNull final Environment environment, @Nullable final BlobVault blobVault, @NotNull final String name) { hashCode = System.identityHashCode(this); this.config = config; this.environment = environment; PersistentEntityStores.adjustEnvironmentConfigForEntityStore(environment.getEnvironmentConfig()); this.name = name; location = environment.getLocation(); namingRulez = new StoreNamingRules(name); iterableCache = new EntityIterableCacheImpl(this); entityIdCache = new ConcurrentObjectCache<>(ENTITY_ID_CACHE_SIZE); explainer = new Explainer(config.isExplainOn()); propertyDataGetter = new PropertyDataGetter(); linkDataGetter = config.isDebugLinkDataGetter() ? new DebugLinkDataGetter() : new LinkDataGetter(); blobDataGetter = new BlobDataGetter(); allSequences = new HashMap<>(); final PersistentStoreTransaction txn = beginTransaction(); sequences = environment.openStore(SEQUENCES_STORE, StoreConfig.WITHOUT_DUPLICATES, txn.getEnvironmentTransaction()); final boolean fromScratch; try { this.blobVault = blobVault == null ? createDefaultFSBlobVault() : blobVault; entitiesSequences = new IntHashMap<>(); final TwoColumnTable entityTypesTable = new TwoColumnTable(txn, namingRulez.getEntityTypesTableName(), StoreConfig.WITHOUT_DUPLICATES); final PersistentSequence entityTypesSequence = getSequence(txn, namingRulez.getEntityTypesSequenceName()); entityTypes = new PersistentSequentialDictionary(entityTypesSequence, entityTypesTable) { @Override protected void created(final PersistentStoreTransaction txn, final int id) { preloadTables(txn, id); } }; propertyIds = new PersistentSequentialDictionary( getSequence(txn, namingRulez.getPropertyIdsSequenceName()), new TwoColumnTable(txn, namingRulez.getPropertyIdsTableName(), StoreConfig.WITHOUT_DUPLICATES)); linkIds = new PersistentSequentialDictionary(getSequence(txn, namingRulez.getLinkIdsSequenceName()), new TwoColumnTable(txn, namingRulez.getLinkIdsTableName(), StoreConfig.WITHOUT_DUPLICATES)); propertyTypes = new PropertyTypes(); propertyCustomTypeIds = new PersistentSequentialDictionary( getSequence(txn, namingRulez.getPropertyCustomTypesSequence()), new TwoColumnTable(txn, namingRulez.getPropertyCustomTypesTable(), StoreConfig.WITHOUT_DUPLICATES_WITH_PREFIXING)); entitiesTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new SingleColumnTable(txn, namingRulez.getEntitiesTableName(entityTypeId), StoreConfig.WITHOUT_DUPLICATES_WITH_PREFIXING); } }); entitiesHistoryTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new SingleColumnTable(txn, namingRulez.getEntitiesHistoryTableName(entityTypeId), StoreConfig.WITH_DUPLICATES); } }); propertiesTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new PropertiesTable(txn, namingRulez.getPropertiesTableName(entityTypeId), StoreConfig.WITHOUT_DUPLICATES); } }); propertiesHistoryTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new SingleColumnTable(txn, namingRulez.getPropertiesHistoryTableName(entityTypeId), StoreConfig.WITHOUT_DUPLICATES); } }); linksTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new TwoColumnTable(txn, namingRulez.getLinksTableName(entityTypeId), StoreConfig.WITH_DUPLICATES_WITH_PREFIXING); } }); linksHistoryTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, int entityTypeId) { return new SingleColumnTable(txn, namingRulez.getLinksHistoryTableName(entityTypeId), StoreConfig.WITH_DUPLICATES_WITH_PREFIXING); } }); blobsTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new BlobsTable(PersistentEntityStoreImpl.this, txn, namingRulez.getBlobsTableName(entityTypeId), StoreConfig.WITHOUT_DUPLICATES); } }); blobsHistoryTables = new OpenTablesCache(new OpenTablesCache.TableCreator() { @NotNull @Override public Table createTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return new SingleColumnTable(txn, namingRulez.getBlobsHistoryTableName(entityTypeId), StoreConfig.WITHOUT_DUPLICATES); } }); final String internalSettingsName = namingRulez.getInternalSettingsName(); final Store settings = environment.openStore(internalSettingsName, StoreConfig.WITHOUT_DUPLICATES, txn.getEnvironmentTransaction(), false); fromScratch = settings == null; if (fromScratch) { internalSettings = environment.openStore(internalSettingsName, StoreConfig.WITHOUT_DUPLICATES, txn.getEnvironmentTransaction(), true); } else { internalSettings = settings; } txn.flush(); } catch (IOException e) { throw ExodusException.toEntityStoreException(e); } finally { txn.abort(); } if (!config.getRefactoringSkipAll()) { applyRefactorings(fromScratch); // this method includes refactorings that could be clustered into separate txns } final PersistentStoreTransaction preloadTxn = beginTransaction(); try { preloadTables(preloadTxn); // this is called to pre-load tables for all entity types to prevent them from being lazy loaded } finally { preloadTxn.commit(); } configMBean = config.isManagementEnabled() ? new EntityStoreConfig(this) : null; startedAt = System.currentTimeMillis(); transactionCount = 0; if (log.isDebugEnabled()) { log.debug("Created successfully."); } } private void applyRefactorings(final boolean fromScratch) { environment.suspendGC(); try { final PersistentEntityStoreRefactorings refactorings = new PersistentEntityStoreRefactorings(this); if (config.getRefactoringDeleteRedundantBlobs()) { refactorings.refactorDeleteRedundantBlobs(); } if (fromScratch || Settings.get(internalSettings, "Entities' stores key-prefixed") == null) { if (!fromScratch) { refactorings.refactorEntitiesTables(); } Settings.set(internalSettings, "Entities' stores key-prefixed", "yes"); } if (fromScratch || Settings.get(internalSettings, "Null-indices present 2") == null || config.getRefactoringNullIndices()) { if (!fromScratch) { Settings.delete(internalSettings, "Null-indices present"); // don't waste space refactorings.refactorCreateNullPropertyIndices(); } Settings.set(internalSettings, "Null-indices present 2", "yes"); } if (fromScratch || Settings.get(internalSettings, "Blobs' null-indices present") == null || config.getRefactoringBlobNullIndices()) { if (!fromScratch) { refactorings.refactorCreateNullBlobIndices(); } Settings.set(internalSettings, "Blobs' null-indices present", "yes"); } if (fromScratch || Settings.get(internalSettings, "Links consistency fixed") == null || config.getRefactoringHeavyLinks()) { if (!fromScratch) { refactorings.refactorMakeLinkTablesConsistent(); } Settings.set(internalSettings, "Links consistency fixed", "yes"); } if (fromScratch || Settings.get(internalSettings, "Props consistency fixed") == null || config.getRefactoringHeavyProps()) { if (!fromScratch) { refactorings.refactorMakePropTablesConsistent(); } Settings.set(internalSettings, "Props consistency fixed", "yes"); } if (blobVault instanceof VFSBlobVault && new File(location, BLOBS_DIR).exists()) { try { ((VFSBlobVault) blobVault).refactorFromFS(this); } catch (IOException e) { throw ExodusException.toEntityStoreException(e); } } if (blobVault instanceof FileSystemBlobVaultOld && !(blobVault instanceof FileSystemBlobVault) && config.getMaxInPlaceBlobSize() > 0) { refactorings.refactorInPlaceBlobs((FileSystemBlobVaultOld) blobVault, BLOB_HANDLES_SEQUENCE); } } finally { environment.resumeGC(); } } private BlobVault createDefaultFSBlobVault() throws IOException { final PersistentSequence sequence = getSequence(getAndCheckCurrentTransaction(), BLOB_HANDLES_SEQUENCE); FileSystemBlobVaultOld blobVault; try { blobVault = new FileSystemBlobVault(location, BLOBS_DIR, BLOBS_EXTENSION, new PersistentSequenceBlobHandleGenerator(sequence)); } catch (UnexpectedBlobVaultVersionException e) { blobVault = null; } if (blobVault == null) { if (config.getMaxInPlaceBlobSize() > 0) { blobVault = new FileSystemBlobVaultOld(location, BLOBS_DIR, BLOBS_EXTENSION, BlobHandleGenerator.IMMUTABLE); } else { blobVault = new FileSystemBlobVaultOld(location, BLOBS_DIR, BLOBS_EXTENSION, new PersistentSequenceBlobHandleGenerator(sequence)); } } final long current = sequence.get(); for (long blobHandle = current + 1; blobHandle < current + 1000; ++blobHandle) { final File file = blobVault.getBlobLocation(blobHandle); if (file.exists()) { log.error("Redundant blob file: " + file); } } return blobVault; } @Override public int hashCode() { return hashCode; } @Override @NotNull public PersistentStoreTransaction beginTransaction() { final PersistentStoreTransaction txn = createTxn(); registerTransaction(txn); return txn; } @NotNull public PersistentStoreTransaction beginReadonlyTransaction() { final PersistentStoreTransaction txn = new ReadonlyPersistentStoreTransaction(this); registerTransaction(txn); return txn; } protected PersistentStoreTransaction createTxn() { return new PersistentStoreTransaction(this); } @Override @Nullable public PersistentStoreTransaction getCurrentTransaction() { final Thread thread = Thread.currentThread(); final Deque<PersistentStoreTransaction> stack = txns.get(thread); return stack == null ? null : stack.peek(); } @NotNull public PersistentStoreTransaction getAndCheckCurrentTransaction() { final PersistentStoreTransaction transaction = getCurrentTransaction(); if (transaction == null) { throw new IllegalStateException("EntityStore: current transaction is not set."); } return transaction; } void registerTransaction(@NotNull final PersistentStoreTransaction txn) { final Thread thread = Thread.currentThread(); Deque<PersistentStoreTransaction> stack = txns.get(thread); if (stack == null) { stack = new ArrayDeque<>(4); txns.put(thread, stack); } stack.push(txn); } void deregisterTransaction(@NotNull final PersistentStoreTransaction txn) { final Thread thread = Thread.currentThread(); final Deque<PersistentStoreTransaction> stack = txns.get(thread); if (stack == null) { throw new EntityStoreException("Transaction was already finished"); } if (txn != stack.peek()) { throw new EntityStoreException("Can't finish transaction: nested transaction is not finished"); } stack.pop(); if (stack.isEmpty()) { txns.remove(thread); } } @Override public void clear() { environment.clear(); } @Override @NotNull public String getName() { return name; } @NotNull @Override public PersistentEntityStoreConfig getConfig() { return config; } @Override @NotNull public String getLocation() { return location; } @NotNull public Environment getEnvironment() { return environment; } @NotNull public PersistentSequence getSequence(@NotNull final PersistentStoreTransaction txn, @NotNull final String sequenceName) { synchronized (allSequences) { PersistentSequence result = allSequences.get(sequenceName); if (result == null) { result = new PersistentSequence(txn, sequences, sequenceName); allSequences.put(sequenceName, result); } return result; } } public List<PersistentSequence> getAllSequences() { synchronized (allSequences) { return new ArrayList<>(allSequences.values()); } } @Override public long getUsableSpace() { return new File(location).getUsableSpace(); } @Override public long getStoreSize() { return environment.getDiskUsage(); } @Override @NotNull public BlobVault getBlobVault() { return blobVault; } void setBlobVault(@NotNull final BlobVault blobVault) { this.blobVault = blobVault; } @Override public void registerCustomPropertyType(@NotNull final StoreTransaction txn, @NotNull final Class<? extends Comparable> clazz, @NotNull final ComparableBinding binding) { propertyTypes.registerCustomPropertyType( propertyCustomTypeIds.getOrAllocateId((PersistentStoreTransaction) txn, clazz.getName()), clazz, binding); } @Override public void executeInTransaction(@NotNull final StoreTransactionalExecutable executable) { PersistentStoreTransaction txn = beginTransaction(); try { do { executable.execute(txn); // if txn has already been aborted in execute() if (txn != getCurrentTransaction()) { txn = null; break; } } while (!txn.flush()); } finally { // if txn has not already been aborted in execute() if (txn != null) { txn.abort(); } } } @Override public void executeInReadonlyTransaction(@NotNull StoreTransactionalExecutable executable) { final PersistentStoreTransaction txn = beginReadonlyTransaction(); try { executable.execute(txn); } finally { // if txn has not already been aborted in execute() if (txn == getCurrentTransaction()) { txn.abort(); } } } @Override public <T> T computeInTransaction(@NotNull final StoreTransactionalComputable<T> computable) { T result; PersistentStoreTransaction txn = beginTransaction(); try { do { result = computable.compute(txn); // if txn has already been aborted in compute() if (txn != getCurrentTransaction()) { txn = null; break; } } while (!txn.flush()); } finally { // if txn has not already been aborted in compute() if (txn != null) { txn.abort(); } } return result; } @Override public <T> T computeInReadonlyTransaction(@NotNull StoreTransactionalComputable<T> computable) { final PersistentStoreTransaction txn = beginReadonlyTransaction(); try { return computable.compute(txn); } finally { // if txn has not already been aborted in compute() if (txn == getCurrentTransaction()) { txn.abort(); } } } @Override public Explainer getExplainer() { return explainer; } @Override @NotNull public EntityIterableCacheImpl getEntityIterableCache() { return iterableCache; } @Nullable public EntityId getCachedEntityId(@NotNull final String representation) { return entityIdCache.tryKey(representation); } public void cacheEntityId(@NotNull final String representation, @NotNull final EntityId id) { entityIdCache.cacheObject(representation, id); } @Nullable Comparable getProperty(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String propertyName) { final int propertyId = getPropertyId(txn, propertyName, false); if (propertyId < 0) { return null; } Comparable result = txn.getCachedProperty(entity, propertyId); if (result == null) { final ByteIterable resultEntry = getRawProperty(txn, entity, propertyId); if (resultEntry != null) { result = propertyTypes.entryToPropertyValue(resultEntry).getData(); txn.cacheProperty(entity.getId(), propertyId, result); } } return result; } @Nullable public ByteIterable getRawProperty(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, final int propertyId) { return getRawValue(txn, entity, propertyId, propertyDataGetter); } public boolean setProperty(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String propertyName, @NotNull final Comparable value) { final int propertyId = getPropertyId(txn, propertyName, true); final ByteIterable oldValueEntry = getRawProperty(txn, entity, propertyId); final Comparable oldValue = oldValueEntry == null ? null : propertyTypes.entryToPropertyValue(oldValueEntry).getData(); if (value.equals(oldValue)) { // value is not null by contract return false; } final PersistentEntityId entityId = entity.getId(); final PropertyValue propValue = propertyTypes.dataToPropertyValue(value); getPropertiesTable(txn, entityId.getTypeId()).put(txn, entityId.getLocalId(), PropertyTypes.propertyValueToEntry(propValue), oldValueEntry, propertyId, propValue.getType()); txn.propertyChanged(entityId, propertyId, oldValue, value); return true; } public boolean setRawProperty(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String propertyName, @NotNull final ByteIterable value) { final Transaction envTxn = txn.getEnvironmentTransaction(); final ByteIterable oldValue = getRawProperty(txn, entity, getPropertyId(txn, propertyName, true)); if (oldValue != null && value.compareTo(oldValue) == 0) { return false; } final EntityId entityId = entity.getId(); final int propertyId = getPropertyId(txn, propertyName, true); getPropertiesTable(txn, entityId.getTypeId()).put(txn, entityId.getLocalId(), value, oldValue, propertyId, null); txn.propertyChanged((PersistentEntityId) entityId, propertyId, oldValue, value); return true; } public boolean deleteProperty(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String propertyName) { final int propertyId = getPropertyId(txn, propertyName, false); if (propertyId < 0) { return false; } final ByteIterable oldValue = getRawProperty(txn, entity, propertyId); if (oldValue == null) { return false; } final EntityId id = entity.getId(); final PropertyValue propValue = propertyTypes.entryToPropertyValue(oldValue); getPropertiesTable(txn, id.getTypeId()).delete(txn, id.getLocalId(), oldValue, propertyId, propValue.getType()); txn.propertyChanged((PersistentEntityId) id, propertyId, propValue.getData(), null); return true; } @NotNull public List<String> getPropertyNames(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final List<String> result = new ArrayList<>(); final EntityId id = entity.getId(); final long entityLocalId = id.getLocalId(); PropertyKey propertyKey = new PropertyKey(entityLocalId, 0); try (Cursor index = getPrimaryPropertyIndexCursor(txn, id.getTypeId())) { for (boolean success = index.getSearchKeyRange( PropertyKey.propertyKeyToEntry(propertyKey)) != null; success; success = index.getNext()) { propertyKey = PropertyKey.entryToPropertyKey(index.getKey()); if (propertyKey.getEntityLocalId() != entityLocalId) { break; } final String propertyName = getPropertyName(txn, propertyKey.getPropertyId()); if (propertyName != null) { result.add(propertyName); } } return result; } } @NotNull public List<Pair<String, Comparable>> getProperties(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final List<Pair<String, Comparable>> result = new ArrayList<>(); final EntityId fromId = entity.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); Cursor cursor = null; try { if (entity.isUpToDate()) { PropertyKey propertyKey = new PropertyKey(entityLocalId, 0); cursor = getPrimaryPropertyIndexCursor(txn, entityTypeId); for (boolean success = cursor.getSearchKeyRange( PropertyKey.propertyKeyToEntry(propertyKey)) != null; success; success = cursor.getNext()) { propertyKey = PropertyKey.entryToPropertyKey(cursor.getKey()); if (propertyKey.getEntityLocalId() != entityLocalId) { break; } final String propertyName = getPropertyName(txn, propertyKey.getPropertyId()); if (propertyName != null) { result.add(new Pair<>(propertyName, propertyTypes.entryToPropertyValue(cursor.getValue()).getData())); } } } else { final int version = entity.getVersion(); PropertyHistoryKey propertyHistoryKey = new PropertyHistoryKey(entityLocalId, version, 0); cursor = getPropertiesHistoryTable(txn, entityTypeId).openCursor(txn.getEnvironmentTransaction()); for (boolean success = cursor.getSearchKeyRange(PropertyHistoryKey .propertyHistoryKeyToEntry(propertyHistoryKey)) != null; success; success = cursor .getNext()) { propertyHistoryKey = PropertyHistoryKey.entryToPropertyHistoryKey(cursor.getKey()); if (propertyHistoryKey.getVersion() != version || propertyHistoryKey.getEntityLocalId() != entityLocalId) { break; } final String propertyName = getPropertyName(txn, propertyHistoryKey.getPropertyId()); if (propertyName != null) { result.add(new Pair<>(propertyName, propertyTypes.entryToPropertyValue(cursor.getValue()).getData())); } } } } finally { if (cursor != null) { cursor.close(); } } return result; } @NotNull public Cursor getPrimaryPropertyIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return getPrimaryPropertyIndexCursor(txn, getPropertiesTable(txn, entityTypeId)); } @NotNull public Cursor getPrimaryPropertyIndexCursor(@NotNull final PersistentStoreTransaction txn, @NotNull final PropertiesTable properties) { return properties.getPrimaryIndex().openCursor(txn.getEnvironmentTransaction()); } @Nullable public Cursor getPropertyValuesIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId, final int propertyId) { final Store valueIdx = getPropertiesTable(txn, entityTypeId).getValueIndex(txn, propertyId, false); if (valueIdx == null) { return null; } return valueIdx.openCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getEntityWithPropCursor(@NotNull final PersistentStoreTransaction txn, int entityTypeId) { return getPropertiesTable(txn, entityTypeId).getAllPropsIndex().openCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getEntityWithBlobCursor(@NotNull final PersistentStoreTransaction txn, int entityTypeId) { return getBlobsTable(txn, entityTypeId).getAllBlobsIndex().openCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getEntitiesIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return getEntitiesTable(txn, entityTypeId).openCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getLinksHistoryIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return getLinksHistoryTable(txn, entityTypeId).openCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getLinksFirstIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return getLinksTable(txn, entityTypeId).getFirstIndexCursor(txn.getEnvironmentTransaction()); } @NotNull public Cursor getLinksSecondIndexCursor(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return getLinksTable(txn, entityTypeId).getSecondIndexCursor(txn.getEnvironmentTransaction()); } /** * Clears all properties of specified entity. * * @param entity to clear. */ @SuppressWarnings({ "OverlyLongMethod" }) public void clearProperties(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final Transaction envTxn = txn.getEnvironmentTransaction(); final PersistentEntityId id = (PersistentEntityId) entity.getId(); final int entityTypeId = id.getTypeId(); final long entityLocalId = id.getLocalId(); if (entity.isUpToDate()) { // up-to-date entity final PropertiesTable properties = getPropertiesTable(txn, entityTypeId); final PropertyKey propertyKey = new PropertyKey(entityLocalId, 0); try (Cursor cursor = getPrimaryPropertyIndexCursor(txn, properties)) { for (boolean success = cursor.getSearchKeyRange( PropertyKey.propertyKeyToEntry(propertyKey)) != null; success; success = cursor.getNext()) { ByteIterable keyEntry = cursor.getKey(); final PropertyKey key = PropertyKey.entryToPropertyKey(keyEntry); if (key.getEntityLocalId() != entityLocalId) { break; } final int propertyId = key.getPropertyId(); final ByteIterable value = cursor.getValue(); final PropertyValue propValue = propertyTypes.entryToPropertyValue(value); txn.propertyChanged(id, propertyId, propValue.getData(), null); properties.deleteNoFail(txn, entityLocalId, value, propertyId, propValue.getType()); } } } else { // historical entity final int version = entity.getVersion(); final Store properties = getPropertiesHistoryTable(txn, entityTypeId); final PropertyHistoryKey propertyKey = new PropertyHistoryKey(entityLocalId, version, 0); ByteIterable keyEntry = PropertyHistoryKey.propertyHistoryKeyToEntry(propertyKey); try (Cursor cursor = properties.openCursor(envTxn)) { for (boolean success = cursor.getSearchKeyRange(keyEntry) != null; success; success = cursor .getNext()) { keyEntry = cursor.getKey(); final PropertyHistoryKey key = PropertyHistoryKey.entryToPropertyHistoryKey(keyEntry); if (key.getEntityLocalId() != entityLocalId || key.getVersion() != version) { break; } properties.delete(envTxn, keyEntry); } } } } public long getBlobSize(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) throws IOException { final Pair<Long, ByteIterator> blobInfo = getBlobHandleAndValue(txn, entity, blobName); if (blobInfo == null) { return -1; } final long blobHandle = blobInfo.getFirst(); if (blobHandle == EMPTY_BLOB_HANDLE) { return 0; } if (blobHandle == IN_PLACE_BLOB_HANDLE) { return CompressedUnsignedLongByteIterable.getLong(blobInfo.getSecond()); } final long result = txn.getBlobSize(blobHandle); if (result < 0) { return blobVault.getSize(blobHandle, txn.getEnvironmentTransaction()); } return result; } @Nullable public InputStream getBlob(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) throws IOException { final Pair<Long, InputStream> blobStream = getInPlaceBlobStream(txn, entity, blobName); if (blobStream == null) { return null; } final long blobHandle = blobStream.getFirst(); if (blobHandle == EMPTY_BLOB_HANDLE) { return EMPTY_INPUT_STREAM; } final InputStream result = blobStream.getSecond(); return result != null ? result : blobVault.getContent(blobHandle, txn.getEnvironmentTransaction()); } @Nullable public String getBlobString(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) throws IOException { final int blobId = getPropertyId(txn, blobName, false); if (blobId < 0) { return null; } String result = txn.getCachedBlobString(entity, blobId); if (result != null) { return result; } final Pair<Long, InputStream> blobStream = getInPlaceBlobStream(txn, entity, blobName); if (blobStream == null) { return null; } final long blobHandle = blobStream.getFirst(); if (blobHandle == EMPTY_BLOB_HANDLE) { result = EMPTY_STRING; } else { try { final InputStream stream = blobStream.getSecond(); if (stream == null) { return blobVault.getStringContent(blobHandle, txn.getEnvironmentTransaction()); } result = UTFUtil.readUTF(stream); } catch (UTFDataFormatException e) { result = e.toString(); } } if (result != null) { txn.cacheBlobString(entity, blobId, result); } return result; } @Nullable Pair<Long, ByteIterator> getBlobHandleAndValue(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) { final int blobId = getPropertyId(txn, blobName, false); if (blobId < 0) { return null; } final ByteIterable valueEntry = getRawValue(txn, entity, blobId, blobDataGetter); if (valueEntry == null) { return null; } final ByteIterator valueIterator = valueEntry instanceof FixedLengthByteIterable ? ((FixedLengthByteIterable) valueEntry).getSource().iterator() : valueEntry.iterator(); return new Pair<>(LongBinding.readCompressed(valueIterator), valueIterator); } @Nullable private Pair<Long, InputStream> getInPlaceBlobStream(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) throws IOException { final Pair<Long, ByteIterator> blobInfo = getBlobHandleAndValue(txn, entity, blobName); if (blobInfo == null) { return null; } final long blobHandle = blobInfo.getFirst(); if (blobHandle == EMPTY_BLOB_HANDLE) { return new Pair<>(blobHandle, null); } if (blobHandle == IN_PLACE_BLOB_HANDLE) { final ByteIterator valueIterator = blobInfo.getSecond(); final int size = (int) CompressedUnsignedLongByteIterable.getLong(valueIterator); return new Pair<Long, InputStream>(blobHandle, new ByteArraySizedInputStream(ByteIterableBase.readIterator(valueIterator, size))); } return new Pair<>(blobHandle, txn.getBlobStream(blobHandle)); } public void setBlob(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName, @NotNull final InputStream stream) throws IOException { final ByteArraySizedInputStream copy = stream instanceof ByteArraySizedInputStream ? (ByteArraySizedInputStream) stream : blobVault.cloneStream(stream, true); final long blobHandle = createBlobHandle(txn, entity, blobName, copy, copy.size()); if (!isEmptyOrInPlaceBlobHandle(blobHandle)) { txn.addBlob(blobHandle, copy); } } public void setBlob(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName, @NotNull final File file) throws IOException { final long length = file.length(); if (length > Integer.MAX_VALUE) { throw new EntityStoreException("Too large blob, size is greater than " + Integer.MAX_VALUE); } final int size = (int) length; final long blobHandle = createBlobHandle(txn, entity, blobName, size > config.getMaxInPlaceBlobSize() ? null : blobVault.cloneStream(new FileInputStream(file), true), size); if (!isEmptyOrInPlaceBlobHandle(blobHandle)) { txn.addBlob(blobHandle, file); } } public void setBlobString(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName, @NotNull final String blobString) throws IOException { final int length = blobString.length(); if (length == 0) { createBlobHandle(txn, entity, blobName, null, 0); } else { final LightByteArrayOutputStream memCopy = new LightByteArrayOutputStream(); UTFUtil.writeUTF(memCopy, blobString); final int streamSize = memCopy.size(); final ByteArraySizedInputStream copy = new ByteArraySizedInputStream(memCopy.toByteArray(), 0, streamSize); final long blobHandle = createBlobHandle(txn, entity, blobName, copy, streamSize); if (!isEmptyOrInPlaceBlobHandle(blobHandle)) { txn.addBlob(blobHandle, copy); } } } private long createBlobHandle(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName, @Nullable final ByteArraySizedInputStream stream, final int size) { final EntityId id = entity.getId(); final long entityLocalId = id.getLocalId(); final int blobId = getPropertyId(txn, blobName, true); txn.invalidateCachedBlobString(entity, blobId); final BlobsTable blobs = getBlobsTable(txn, id.getTypeId()); final Transaction envTxn = txn.getEnvironmentTransaction(); final ByteIterable value = blobs.get(envTxn, entityLocalId, blobId); if (value != null) { deleteObsoleteBlobHandle(LongBinding.compressedEntryToLong(value), txn); } final long blobHandle; if (size == 0) { blobHandle = EMPTY_BLOB_HANDLE; blobs.put(envTxn, entityLocalId, blobId, LongBinding.longToCompressedEntry(blobHandle)); } else if (size <= config.getMaxInPlaceBlobSize()) { if (stream == null) { throw new NullPointerException("In-memory blob content is expected"); } blobHandle = IN_PLACE_BLOB_HANDLE; blobs.put(envTxn, entityLocalId, blobId, new CompoundByteIterable(new ByteIterable[] { LongBinding.longToCompressedEntry(blobHandle), CompressedUnsignedLongByteIterable.getIterable(size), new ArrayByteIterable(stream.toByteArray(), size) })); } else { blobHandle = blobVault.nextHandle(envTxn); blobs.put(envTxn, entityLocalId, blobId, LongBinding.longToCompressedEntry(blobHandle)); } return blobHandle; } public boolean deleteBlob(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity, @NotNull final String blobName) { final Transaction envTxn = txn.getEnvironmentTransaction(); final int blobId = getPropertyId(txn, blobName, false); if (blobId < 0) { return false; } txn.invalidateCachedBlobString(entity, blobId); final EntityId id = entity.getId(); final long entityLocalId = id.getLocalId(); final BlobsTable blobs = getBlobsTable(txn, id.getTypeId()); final ByteIterable value = blobs.get(envTxn, entityLocalId, blobId); if (value == null) { return false; } blobs.delete(envTxn, entityLocalId, blobId); deleteObsoleteBlobHandle(LongBinding.compressedEntryToLong(value), txn); return true; } @SuppressWarnings({ "OverlyLongMethod" }) public void clearBlobs(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity) { final EntityId id = entity.getId(); final int entityTypeId = id.getTypeId(); final long entityLocalId = id.getLocalId(); final Transaction envTxn = txn.getEnvironmentTransaction(); if (entity.isUpToDate()) { final BlobsTable blobs = getBlobsTable(txn, entityTypeId); final PropertyKey propertyKey = new PropertyKey(entityLocalId, 0); ByteIterable keyEntry = PropertyKey.propertyKeyToEntry(propertyKey); try (Cursor cursor = blobs.getPrimaryIndex().openCursor(envTxn)) { for (boolean success = cursor.getSearchKeyRange(keyEntry) != null; success; success = cursor .getNext()) { keyEntry = cursor.getKey(); final PropertyKey key = PropertyKey.entryToPropertyKey(keyEntry); if (key.getEntityLocalId() != entityLocalId) { break; } final ByteIterable value = cursor.getValue(); final int blobId = key.getPropertyId(); blobs.delete(envTxn, entityLocalId, blobId); txn.invalidateCachedBlobString(entity, blobId); deleteObsoleteBlobHandle(LongBinding.compressedEntryToLong(value), txn); } } } else { final Store blobs = getBlobsHistoryTable(txn, entityTypeId); final int version = entity.getVersion(); final PropertyHistoryKey propertyKey = new PropertyHistoryKey(entityLocalId, version, 0); ByteIterable keyEntry = PropertyHistoryKey.propertyHistoryKeyToEntry(propertyKey); try (Cursor cursor = blobs.openCursor(envTxn)) { for (boolean success = cursor.getSearchKeyRange(keyEntry) != null; success; success = cursor .getNext()) { keyEntry = cursor.getKey(); final PropertyHistoryKey key = PropertyHistoryKey.entryToPropertyHistoryKey(keyEntry); if (key.getEntityLocalId() != entityLocalId || key.getVersion() != version) { break; } final ByteIterable value = cursor.getValue(); blobs.delete(envTxn, keyEntry); deleteObsoleteBlobHandle(LongBinding.compressedEntryToLong(value), txn); } } } } @NotNull public List<String> getBlobNames(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity) { final List<String> result = new ArrayList<>(); final EntityId id = entity.getId(); final long entityLocalId = id.getLocalId(); PropertyKey propertyKey = new PropertyKey(entityLocalId, 0); try (Cursor index = getBlobsTable(txn, id.getTypeId()).getPrimaryIndex() .openCursor(txn.getEnvironmentTransaction())) { for (boolean success = index.getSearchKeyRange( PropertyKey.propertyKeyToEntry(propertyKey)) != null; success; success = index.getNext()) { propertyKey = PropertyKey.entryToPropertyKey(index.getKey()); if (propertyKey.getEntityLocalId() != entityLocalId) { break; } final String propertyName = getPropertyName(txn, propertyKey.getPropertyId()); if (propertyName != null) { result.add(propertyName); } } return result; } } @NotNull public List<Pair<String, Long>> getBlobs(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final List<Pair<String, Long>> result = new ArrayList<>(); final EntityId fromId = entity.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); Cursor cursor = null; try { if (entity.isUpToDate()) { PropertyKey blobKey = new PropertyKey(entityLocalId, 0); cursor = getBlobsTable(txn, entityTypeId).getPrimaryIndex() .openCursor(txn.getEnvironmentTransaction()); for (boolean success = cursor.getSearchKeyRange( PropertyKey.propertyKeyToEntry(blobKey)) != null; success; success = cursor.getNext()) { blobKey = PropertyKey.entryToPropertyKey(cursor.getKey()); if (blobKey.getEntityLocalId() != entityLocalId) { break; } final String blobName = getPropertyName(txn, blobKey.getPropertyId()); if (blobName != null) { result.add(new Pair<>(blobName, LongBinding.compressedEntryToLong(cursor.getValue()))); } } } else { final int version = entity.getVersion(); PropertyHistoryKey blobHistoryKey = new PropertyHistoryKey(entityLocalId, version, 0); cursor = getBlobsHistoryTable(txn, entityTypeId).openCursor(txn.getEnvironmentTransaction()); for (boolean success = cursor.getSearchKeyRange(PropertyHistoryKey .propertyHistoryKeyToEntry(blobHistoryKey)) != null; success; success = cursor.getNext()) { blobHistoryKey = PropertyHistoryKey.entryToPropertyHistoryKey(cursor.getKey()); if (blobHistoryKey.getVersion() != version || blobHistoryKey.getEntityLocalId() != entityLocalId) { break; } final String blobName = getPropertyName(txn, blobHistoryKey.getPropertyId()); if (blobName != null) { result.add(new Pair<>(blobName, LongBinding.compressedEntryToLong(cursor.getValue()))); } } } } finally { if (cursor != null) { cursor.close(); } } return result; } boolean addLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, @NotNull final PersistentEntity to, final int linkId) { final EntityId fromId = from.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); final PropertyKey linkKey = new PropertyKey(entityLocalId, linkId); final LinkValue linkValue = new LinkValue(to.getId(), linkId); if (!getLinksTable(txn, entityTypeId).put(txn.getEnvironmentTransaction(), PropertyKey.propertyKeyToEntry(linkKey), LinkValue.linkValueToEntry(linkValue))) { return false; } txn.linkAdded(from.getId(), to.getId(), linkId); return true; } boolean setLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final int linkId, @Nullable final PersistentEntity to) { final Transaction envTxn = txn.getEnvironmentTransaction(); final PersistentEntityId fromId = from.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); final ByteIterable keyEntry = PropertyKey.propertyKeyToEntry(new PropertyKey(entityLocalId, linkId)); final TwoColumnTable links = getLinksTable(txn, entityTypeId); boolean oldTargetDeleted = false; final ByteIterable valueEntry = getRawLink(txn, from, linkId); if (valueEntry != null) { final PersistentEntity oldTarget = getEntity(LinkValue.entryToLinkValue(valueEntry).getEntityId()); if (oldTarget.equals(to)) { return false; } links.delete(envTxn, keyEntry, valueEntry); txn.linkDeleted(fromId, oldTarget.getId(), linkId); oldTargetDeleted = true; } if (to == null) { return oldTargetDeleted; } final LinkValue linkValue = new LinkValue(to.getId(), linkId); links.put(envTxn, keyEntry, LinkValue.linkValueToEntry(linkValue)); txn.linkAdded(fromId, to.getId(), linkId); return true; } @Nullable PersistentEntity getLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final int linkId) { final PersistentEntityId resultId = getLinkAsEntityId(txn, from, linkId); return resultId == null ? null : getEntity(resultId); } @Nullable public PersistentEntityId getLinkAsEntityId(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, int linkId) { PersistentEntityId resultId = txn.getCachedLink(from, linkId); if (resultId == null) { final ByteIterable resultEntry = getRawLink(txn, from, linkId); if (resultEntry != null) { resultId = (PersistentEntityId) LinkValue.entryToLinkValue(resultEntry).getEntityId(); txn.cacheLink(from, linkId, resultId); } } return resultId; } @Nullable private ByteIterable getRawLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final int linkId) { return getRawValue(txn, from, linkId, linkDataGetter); } @Nullable private ByteIterable getRawValue(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity, final int propertyId, @NotNull final DataGetter dataGetter) { final EntityId fromId = entity.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); if (entity.isUpToDate()) { return dataGetter.getUpToDateEntry(txn, entityTypeId, new PropertyKey(entityLocalId, propertyId)); } else { final int version = entity.getVersion(); final int nextVersion; ByteIterable keyEntry; try (Cursor cursor = getEntitiesHistoryTable(txn, entityTypeId) .openCursor(txn.getEnvironmentTransaction())) { keyEntry = LongBinding.longToCompressedEntry(entityLocalId); final ByteIterable valueEntry = IntegerBinding.intToCompressedEntry(version); if (cursor.getSearchBothRange(keyEntry, valueEntry) == null) { // key and value entries unchanged return dataGetter.getUpToDateEntry(txn, entityTypeId, new PropertyKey(entityLocalId, propertyId)); } nextVersion = IntegerBinding.compressedEntryToInt(cursor.getValue()); } keyEntry = PropertyHistoryKey .propertyHistoryKeyToEntry(new PropertyHistoryKey(entityLocalId, nextVersion, propertyId)); return dataGetter.getHistory(txn, entityTypeId).get(txn.getEnvironmentTransaction(), keyEntry); } } @NotNull EntityIterableBase getLinks(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final int linkId) { final EntityId fromId = from.getId(); if (from.isUpToDate()) { // up-to-date entity return new EntityFromLinksIterable(txn, this, fromId, linkId); } else { // historical entity final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); final int version = from.getVersion(); final int nextVersion; try (final Cursor cursor = getEntitiesHistoryTable(txn, entityTypeId) .openCursor(txn.getEnvironmentTransaction())) { final ByteIterable keyEntry = LongBinding.longToCompressedEntry(entityLocalId); final ByteIterable valueEntry = IntegerBinding.intToCompressedEntry(version); if (cursor.getSearchBothRange(keyEntry, valueEntry) == null) { return new EntityFromLinksIterable(txn, this, fromId, linkId); } nextVersion = IntegerBinding.compressedEntryToInt(valueEntry); } return new EntityFromHistoryLinksIterable(this, fromId, nextVersion, linkId); } } @NotNull EntityIterable getLinks(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final IntHashMap<String> linkNames) { final EntityId fromId = from.getId(); if (from.isUpToDate()) { // up-to-date entity return new EntityFromLinkSetIterable(txn, this, fromId, linkNames); } else { // historical entity final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); final int version = from.getVersion(); final int nextVersion; try (Cursor cursor = getEntitiesHistoryTable(txn, entityTypeId) .openCursor(txn.getEnvironmentTransaction())) { final ByteIterable keyEntry = LongBinding.longToCompressedEntry(entityLocalId); final ByteIterable valueEntry = IntegerBinding.intToCompressedEntry(version); if (cursor.getSearchBothRange(keyEntry, valueEntry) == null) { return new EntityFromLinkSetIterable(txn, this, fromId, linkNames); } nextVersion = IntegerBinding.compressedEntryToInt(valueEntry); } return new EntityFromHistoryLinkSetIterable(this, fromId, nextVersion, linkNames); } } boolean deleteLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, final int linkId, @NotNull final PersistentEntity to) { return deleteLink(txn, from, linkId, to.getId()); } boolean deleteLink(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity from, int linkId, @NotNull final PersistentEntityId toId) { final PersistentEntityId fromId = from.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); final PropertyKey linkKey = new PropertyKey(entityLocalId, linkId); final LinkValue linkValue = new LinkValue(toId, linkId); final Transaction envTxn = txn.getEnvironmentTransaction(); final TwoColumnTable links = getLinksTable(txn, entityTypeId); if (links.delete(envTxn, PropertyKey.propertyKeyToEntry(linkKey), LinkValue.linkValueToEntry(linkValue))) { txn.linkDeleted(fromId, toId, linkId); return true; } return false; } @NotNull public List<String> getLinkNames(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final List<String> result = new ArrayList<>(); final EntityId id = entity.getId(); final long entityLocalId = id.getLocalId(); PropertyKey linkKey = new PropertyKey(entityLocalId, 0); try (final Cursor index = getLinksTable(txn, id.getTypeId()) .getFirstIndexCursor(txn.getEnvironmentTransaction())) { for (boolean success = index.getSearchKeyRange( PropertyKey.propertyKeyToEntry(linkKey)) != null; success; success = index.getNextNoDup()) { linkKey = PropertyKey.entryToPropertyKey(index.getKey()); if (linkKey.getEntityLocalId() != entityLocalId) { break; } final String linkName = getLinkName(txn, linkKey.getPropertyId()); if (linkName != null) { result.add(linkName); } } return result; } } @NotNull public List<Pair<String, EntityId>> getLinks(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { final List<Pair<String, EntityId>> result = new ArrayList<>(); final EntityId fromId = entity.getId(); final int entityTypeId = fromId.getTypeId(); final long entityLocalId = fromId.getLocalId(); Cursor cursor = null; try { if (entity.isUpToDate()) { PropertyKey linkKey = new PropertyKey(entityLocalId, 0); cursor = getLinksTable(txn, entityTypeId).getFirstIndexCursor(txn.getEnvironmentTransaction()); for (boolean success = cursor.getSearchKeyRange( PropertyKey.propertyKeyToEntry(linkKey)) != null; success; success = cursor.getNext()) { linkKey = PropertyKey.entryToPropertyKey(cursor.getKey()); if (linkKey.getEntityLocalId() != entityLocalId) { break; } final String linkName = getLinkName(txn, linkKey.getPropertyId()); if (linkName != null) { result.add( new Pair<>(linkName, LinkValue.entryToLinkValue(cursor.getValue()).getEntityId())); } } } else { final int version = entity.getVersion(); PropertyHistoryKey linkHistoryKey = new PropertyHistoryKey(entityLocalId, version, 0); cursor = getLinksHistoryIndexCursor(txn, entityTypeId); for (boolean success = cursor.getSearchKeyRange(PropertyHistoryKey .propertyHistoryKeyToEntry(linkHistoryKey)) != null; success; success = cursor.getNext()) { linkHistoryKey = PropertyHistoryKey.entryToPropertyHistoryKey(cursor.getKey()); if (linkHistoryKey.getVersion() != version || linkHistoryKey.getEntityLocalId() != entityLocalId) { break; } final String linkName = getLinkName(txn, linkHistoryKey.getPropertyId()); if (linkName != null) { result.add( new Pair<>(linkName, LinkValue.entryToLinkValue(cursor.getValue()).getEntityId())); } } } } finally { if (cursor != null) { cursor.close(); } } return result; } @Override @Deprecated public int getLastVersion(@NotNull final EntityId id) { return getLastVersion(getAndCheckCurrentTransaction(), id); } int getLastVersion(@NotNull final PersistentStoreTransaction txn, @NotNull final EntityId id) { final Store entities = getEntitiesTable(txn, id.getTypeId()); final ByteIterable versionEntry = entities.get(txn.getEnvironmentTransaction(), LongBinding.longToCompressedEntry(id.getLocalId())); if (versionEntry == null) { return -1; } return IntegerBinding.compressedEntryToInt(versionEntry); } @Override public PersistentEntity getEntity(@NotNull final EntityId id) { return new PersistentEntity(this, (PersistentEntityId) id); } /** * Creates new version of the specified entity copying all its properties, * blobs and links to the history. * * @param entity specified entity. */ public void newVersion(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentStoreTransaction snapshot, @NotNull final PersistentEntity entity) { final Transaction envTxn = txn.getEnvironmentTransaction(); final EntityId id = entity.getId(); final int entityTypeId = id.getTypeId(); final long localId = id.getLocalId(); final Store entitiesTable = getEntitiesTable(snapshot, entityTypeId); final ByteIterable key = LongBinding.longToCompressedEntry(localId); final ByteIterable versionEntry = entitiesTable.get(snapshot.getEnvironmentTransaction(), key); if (versionEntry == null) { throw new IllegalStateException("Entity wasn't saved to database yet, can't create history snapshot"); } final int version = IntegerBinding.compressedEntryToInt(versionEntry); entitiesTable.put(envTxn, key, IntegerBinding.intToCompressedEntry(version + 1)); final Store entitiesHistory = getEntitiesHistoryTable(txn, entityTypeId); entitiesHistory.put(envTxn, LongBinding.longToCompressedEntry(localId), IntegerBinding.intToCompressedEntry(version)); //copy properties final Store propertiesHistory = getPropertiesHistoryTable(txn, entityTypeId); for (final String propName : getPropertyNames(snapshot, entity)) { final int propertyId = getPropertyId(txn, propName, false); if (propertyId < 0) continue; final ByteIterable entry = getRawProperty(snapshot, entity, propertyId); if (entry != null) { propertiesHistory.put(envTxn, PropertyHistoryKey.propertyHistoryKeyToEntry( new PropertyHistoryKey(localId, version, getPropertyId(txn, propName, false))), entry); } } //copy blobs final Store blobsHistory = getBlobsHistoryTable(txn, entityTypeId); for (final String blobName : getBlobNames(snapshot, entity)) { final int blobId = getPropertyId(txn, blobName, false); if (blobId < 0) { continue; } final ByteIterable valueEntry = getRawValue(txn, entity, blobId, blobDataGetter); if (valueEntry == null) { continue; } final long blobHandle = LongBinding.compressedEntryToLong(valueEntry); txn.preserveBlob(blobHandle); blobsHistory.put(envTxn, PropertyHistoryKey.propertyHistoryKeyToEntry( new PropertyHistoryKey(localId, version, getPropertyId(txn, blobName, false))), valueEntry); } //copy links final Store linksHistory = getLinksHistoryTable(txn, entityTypeId); for (final String linkName : getLinkNames(snapshot, entity)) { final int linkId = getLinkId(snapshot, linkName, false); if (linkId < 0) { continue; } final ByteIterable linkHistoryKey = PropertyHistoryKey .propertyHistoryKeyToEntry(new PropertyHistoryKey(localId, version, linkId)); final EntityIterator itr = getLinks(snapshot, entity, linkId).getIteratorImpl(snapshot); while (itr.hasNext()) { linksHistory.put(envTxn, linkHistoryKey, LinkValue.linkValueToEntry(new LinkValue(itr.nextId(), linkId))); } } } @Nullable public Entity getNextVersion(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity) { final EntityId id = entity.getId(); final int version = entity.getVersion(); final int lastVersion = getLastVersion(txn, id); if (lastVersion >= 0) { // if specified entity is up-to-date if (version == lastVersion) { return null; } try (Cursor cursor = getEntitiesHistoryTable(txn, id.getTypeId()) .openCursor(txn.getEnvironmentTransaction())) { boolean success = cursor.getSearchBothRange(LongBinding.longToCompressedEntry(id.getLocalId()), IntegerBinding.intToCompressedEntry(version)) != null; if (success) { if (version == IntegerBinding.compressedEntryToInt(cursor.getValue())) { success = cursor.getNext(); } if (success) { final long localId = LongBinding.compressedEntryToLong(cursor.getKey()); if (localId == id.getLocalId()) { return new PersistentEntity(this, new PersistentEntityId(id, IntegerBinding.compressedEntryToInt(cursor.getValue()))); } } } // return up-to-date version if there is no more historic version newer than specified one return getEntity(new PersistentEntityId(id)); } } throw new EntityStoreException( "getNextVersion(): could not find history version = " + version + ", id = " + id); } @Nullable public Entity getPreviousVersion(@NotNull final PersistentStoreTransaction txn, @NotNull final Entity entity) { int version = entity.getVersion(); final EntityId id = entity.getId(); try (Cursor cursor = getEntitiesHistoryTable(txn, id.getTypeId()) .openCursor(txn.getEnvironmentTransaction())) { final ByteIterable keyEntry = LongBinding.longToCompressedEntry(id.getLocalId()); while (version > 0) { final ByteIterable valueEntry = IntegerBinding.intToCompressedEntry(--version); if (cursor.getSearchBoth(keyEntry, valueEntry)) { return new PersistentEntity(this, new PersistentEntityId(id, version)); } } return null; } } /** * Deletes specified entity clearing all its properties and deleting all its outgoing links. * * @param entity to delete. */ boolean deleteEntity(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity) { final Transaction envTxn = txn.getEnvironmentTransaction(); final Entity prev = getPreviousVersion(txn, entity); if (prev != null) { deleteEntity(txn, (PersistentEntity) prev); } clearProperties(txn, entity); clearBlobs(txn, entity); deleteLinks(txn, entity); final PersistentEntityId id = entity.getId(); final int entityTypeId = id.getTypeId(); final long entityLocalId = id.getLocalId(); final ByteIterable key = LongBinding.longToCompressedEntry(entityLocalId); if (entity.isUpToDate()) { // up-to-date entity if (getEntitiesTable(txn, entityTypeId).delete(envTxn, key)) { txn.entityDeleted(id); return true; } return false; } else { // historical entity return getEntitiesHistoryTable(txn, entityTypeId).delete(envTxn, key); } } /** * Deletes all outgoing links of specified entity. * * @param entity the entity. */ @SuppressWarnings({ "OverlyLongMethod" }) private void deleteLinks(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentEntity entity) { final PersistentEntityId id = entity.getId(); final int entityTypeId = id.getTypeId(); final long entityLocalId = id.getLocalId(); final Transaction envTxn = txn.getEnvironmentTransaction(); if (entity.isUpToDate()) { // up-to-date entity final TwoColumnTable links = getLinksTable(txn, entityTypeId); final PropertyKey linkKey = new PropertyKey(entityLocalId, 0); ByteIterable keyEntry = PropertyKey.propertyKeyToEntry(linkKey); try (Cursor cursor = links.getFirstIndexCursor(envTxn)) { for (boolean success = cursor.getSearchKeyRange(keyEntry) != null; success; success = cursor .getNext()) { keyEntry = cursor.getKey(); final PropertyKey key = PropertyKey.entryToPropertyKey(keyEntry); if (key.getEntityLocalId() != entityLocalId) { break; } final ByteIterable valueEntry = cursor.getValue(); if (links.delete(envTxn, keyEntry, valueEntry)) { if (getLinkName(txn, key.getPropertyId()) != null) { final LinkValue linkValue = LinkValue.entryToLinkValue(valueEntry); txn.linkDeleted(entity.getId(), (PersistentEntityId) linkValue.getEntityId(), linkValue.getLinkId()); } } } } } else { // historical entity final int version = entity.getVersion(); final Store links = getLinksHistoryTable(txn, entityTypeId); final PropertyHistoryKey propertyKey = new PropertyHistoryKey(entityLocalId, version, 0); ByteIterable keyEntry = PropertyHistoryKey.propertyHistoryKeyToEntry(propertyKey); try (Cursor cursor = links.openCursor(envTxn)) { for (boolean success = cursor.getSearchKeyRange(keyEntry) != null; success; success = cursor .getNext()) { keyEntry = cursor.getKey(); final PropertyHistoryKey key = PropertyHistoryKey.entryToPropertyHistoryKey(keyEntry); if (key.getEntityLocalId() != entityLocalId || key.getVersion() != version) { break; } links.delete(txn.getEnvironmentTransaction(), keyEntry); } } } } /** * Gets or creates id of the entity type. * * @param entityType entity type name. * @param allowCreate if set to true and if there is no entity type like entityType, * create the new id for the entityType. * @return entity type id. */ @Override @Deprecated public int getEntityTypeId(@NotNull final String entityType, final boolean allowCreate) { return getEntityTypeId(getAndCheckCurrentTransaction(), entityType, allowCreate); } public int getEntityTypeId(@NotNull final PersistentStoreTransaction txn, @NotNull final String entityType, final boolean allowCreate) { return allowCreate ? entityTypes.getOrAllocateId(txn, entityType) : entityTypes.getId(txn, entityType); } /** * Gets type name of the entity type id. * * @param entityTypeId id of the entity type. * @return entity type name. */ @Override @NotNull @Deprecated public String getEntityType(final int entityTypeId) { return getEntityType(getAndCheckCurrentTransaction(), entityTypeId); } @NotNull public String getEntityType(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { final String result = entityTypes.getName(txn, entityTypeId); if (result == null) { throw new EntityStoreException("Invalid type id: " + entityTypeId); } return result; } /** * @return all entity types available. */ @Deprecated @Override @NotNull public List<String> getEntityTypes() { return getEntityTypes(getAndCheckCurrentTransaction()); } /** * @return all entity types available. */ @NotNull public List<String> getEntityTypes(@NotNull final PersistentStoreTransaction txn) { final List<String> result = new ArrayList<>(); try (Cursor entityTypesCursor = entityTypes.getTable() .getSecondIndexCursor(txn.getEnvironmentTransaction())) { while (entityTypesCursor.getNext()) { final int entityTypeId = IntegerBinding.compressedEntryToInt(entityTypesCursor.getKey()); result.add(getEntityType(txn, entityTypeId)); } } return result; } @Override public void renameEntityType(@NotNull final String oldEntityTypeName, @NotNull final String newEntityTypeName) { entityTypes.rename(getAndCheckCurrentTransaction(), oldEntityTypeName, newEntityTypeName); } public void deleteEntityType(@NotNull final String entityTypeName) { final PersistentStoreTransaction txn = getAndCheckCurrentTransaction(); final int entityTypeId = entityTypes.delete(txn, entityTypeName); if (entityTypeId < 0) { return; } entitiesTables.remove(entityTypeId); entitiesHistoryTables.remove(entityTypeId); propertiesTables.remove(entityTypeId); propertiesHistoryTables.remove(entityTypeId); linksTables.remove(entityTypeId); linksHistoryTables.remove(entityTypeId); blobsTables.remove(entityTypeId); blobsHistoryTables.remove(entityTypeId); final String entityTableName = namingRulez.getEntitiesTableName(entityTypeId); final String propertiesTableName = namingRulez.getPropertiesTableName(entityTypeId); final String linksTableName = namingRulez.getLinksTableName(entityTypeId); final String secondLinksTableName = TwoColumnTable.secondColumnDatabaseName(linksTableName); final String blobsTableName = namingRulez.getBlobsTableName(entityTypeId); truncateStores(txn, Arrays.<String>asList(entityTableName, linksTableName, secondLinksTableName, propertiesTableName, blobsTableName), new Iterable<String>() { @Override public Iterator<String> iterator() { return new Iterator<String>() { // enumerate all property value indexes private int propertyId = 0; @Override public boolean hasNext() { return propertyId < 10000; // this was taken from } @Override public String next() { return propertiesTableName + "#value_idx" + propertyId++; } @Override public void remove() { // don't give a damn } }; } }); } private void truncateStores(@NotNull final PersistentStoreTransaction txn, @NotNull Iterable<String> unsafe, @NotNull Iterable<String> safe) { final Transaction envTxn = txn.getEnvironmentTransaction(); for (final String name : unsafe) { environment.truncateStore(name, envTxn); } for (final String name : safe) { try { environment.truncateStore(name, envTxn); } catch (ExodusException e) { //ignore } } } @Deprecated public int getPropertyId(@NotNull final String propertyName, final boolean allowCreate) { return getPropertyId(getAndCheckCurrentTransaction(), propertyName, allowCreate); } /** * Gets id of a property and creates the new one if necessary. * * @param txn transaction * @param propertyName name of the property. * @param allowCreate if set to true and if there is no property named as propertyName, * create the new id for the propertyName. * @return < 0 if there is no such property and create=false, else id of the property */ public int getPropertyId(@NotNull final PersistentStoreTransaction txn, @NotNull final String propertyName, final boolean allowCreate) { return allowCreate ? propertyIds.getOrAllocateId(txn, propertyName) : propertyIds.getId(txn, propertyName); } @Nullable public String getPropertyName(@NotNull final PersistentStoreTransaction txn, final int propertyId) { return propertyIds.getName(txn, propertyId); } @Deprecated public int getLinkId(@NotNull final String linkName, final boolean allowCreate) { return getLinkId(getAndCheckCurrentTransaction(), linkName, allowCreate); } /** * Gets id of a link and creates the new one if necessary. * * @param linkName name of the link. * @param allowCreate if set to true and if there is no link named as linkName, * create the new id for the linkName. * @return < 0 if there is no such link and create=false, else id of the link */ public int getLinkId(@NotNull final PersistentStoreTransaction txn, @NotNull final String linkName, final boolean allowCreate) { return allowCreate ? linkIds.getOrAllocateId(txn, linkName) : linkIds.getId(txn, linkName); } @Nullable String getLinkName(@NotNull final PersistentStoreTransaction txn, final int linkId) { return linkIds.getName(txn, linkId); } public List<String> getAllLinkNames(@NotNull final PersistentStoreTransaction txn) { final int lastLinkId = linkIds.getLastAllocatedId(); final List<String> result = new ArrayList<>(lastLinkId + 1); for (int i = 0; i <= lastLinkId; ++i) { final String linkName = getLinkName(txn, i); if (linkName != null) { result.add(linkName); } } return result; } void clearHistory(@NotNull final PersistentStoreTransaction txn, @NotNull final String entityType) { final int entityTypeId = getEntityTypeId(txn, entityType, false); if (entityTypeId < 0) { return; } try { // set versions of all entities to zero final Store entitiesTable = getEntitiesTable(txn, entityTypeId); final Transaction envTxn = txn.getEnvironmentTransaction(); try (Cursor entities = entitiesTable.openCursor(envTxn)) { while (entities.getNext()) { entitiesTable.put(envTxn, entities.getKey(), PersistentStoreTransaction.ZERO_VERSION_ENTRY); } } safeTruncateStore(txn, namingRulez.getEntitiesHistoryTableName(entityTypeId)); safeTruncateStore(txn, namingRulez.getPropertiesHistoryTableName(entityTypeId)); safeTruncateStore(txn, namingRulez.getBlobsHistoryTableName(entityTypeId)); safeTruncateStore(txn, namingRulez.getLinksHistoryTableName(entityTypeId)); } catch (Throwable t) { throw ExodusException.toEntityStoreException(t); } } @Override public void updateUniqueKeyIndices(@NotNull final Iterable<Index> indices) { environment.suspendGC(); try { executeInTransaction(new StoreTransactionalExecutable() { @Override public void execute(@NotNull StoreTransaction txn) { final PersistentStoreTransaction t = (PersistentStoreTransaction) txn; final PersistentStoreTransaction snapshot = t.getSnapshot(); try { final Collection<String> indexNames = new HashSet<>(); for (final String dbName : environment.getAllStoreNames(t.getEnvironmentTransaction())) { if (namingRulez.isUniqueKeyIndexName(dbName)) { indexNames.add(dbName); } } for (final Index index : indices) { final String indexName = getUniqueKeyIndexName(index); if (indexNames.contains(indexName)) { indexNames.remove(indexName); } else { createUniqueKeyIndex(t, snapshot, index); } } // remove obsolete indices for (final String indexName : indexNames) { removeObsoleteUniqueKeyIndex(t, indexName); } if (log.isTraceEnabled()) { log.trace("Flush index persistent transaction " + t); } t.flush(); } finally { snapshot.abort(); // reading snapshot is obsolete now } } }); } finally { environment.resumeGC(); } } private void removeObsoleteUniqueKeyIndex(@NotNull final PersistentStoreTransaction txn, @NotNull final String indexName) { if (log.isDebugEnabled()) { log.debug("Remove obsolete index [" + indexName + ']'); } environment.removeStore(indexName, txn.getEnvironmentTransaction()); } @SuppressWarnings({ "OverlyLongMethod", "ThrowCaughtLocally", "OverlyNestedMethod", "ConstantConditions" }) private void createUniqueKeyIndex(@NotNull final PersistentStoreTransaction txn, @NotNull final PersistentStoreTransaction snapshot, @NotNull final Index index) { if (log.isDebugEnabled()) { log.debug("Create index [" + index + ']'); } final List<IndexField> fields = index.getFields(); final int propCount = fields.size(); if (propCount == 0) { throw new EntityStoreException("Can't create unique key index on empty list of keys."); } SingleColumnTable indexTable = null; Comparable[] props = new Comparable[propCount]; for (final String entityType : index.getEntityTypesToIndex()) { int i = 0; for (final Entity entity : snapshot.getAll(entityType)) { for (int j = 0; j < propCount; ++j) { final IndexField field = fields.get(j); if (field.isProperty()) { if ((props[j] = getProperty(txn, (PersistentEntity) entity, field.getName())) == null) { throw new EntityStoreException( "Can't create unique key index with null property value: " + entityType + '.' + field.getName()); } } else { if ((props[j] = entity.getLink(field.getName())) == null) { throw new EntityStoreException("Can't create unique key index with null link: " + entityType + '.' + field.getName()); } } } if (indexTable == null) { final String uniqueKeyIndexName = getUniqueKeyIndexName(index); indexTable = new SingleColumnTable(txn, uniqueKeyIndexName, environment.storeExists(uniqueKeyIndexName, txn.getEnvironmentTransaction()) ? StoreConfig.USE_EXISTING : config.getUniqueIndicesUseBtree() ? StoreConfig.WITHOUT_DUPLICATES : StoreConfig.WITHOUT_DUPLICATES_WITH_PREFIXING); } if (!indexTable.getDatabase().add(txn.getEnvironmentTransaction(), propertyTypes.dataArrayToEntry(props), LongBinding.longToCompressedEntry(entity.getId().getLocalId()))) { throw new EntityStoreException("Failed to insert unique key (already exists), index: " + index + ", values = " + Arrays.toString(props)); } if (++i % 100 == 0) { txn.flush(); } } txn.flush(); } } void insertUniqueKey(@NotNull final PersistentStoreTransaction txn, @NotNull final Index index, @NotNull final List<Comparable> propValues, @NotNull final Entity entity) { final int propCount = index.getFields().size(); if (propCount != propValues.size()) { throw new IllegalArgumentException("Number of fields differs from the number of property values"); } final Store indexTable = getUniqueKeyIndex(txn, index); if (!indexTable.add(txn.getEnvironmentTransaction(), propertyTypes.dataArrayToEntry(propValues.toArray(new Comparable[propCount])), LongBinding.longToCompressedEntry(entity.getId().getLocalId()))) { throw new InsertConstraintException("Failed to insert unique key (already exists). Index: " + index); } } void deleteUniqueKey(@NotNull final PersistentStoreTransaction txn, @NotNull final Index index, @NotNull final List<Comparable> propValues) { final int propCount = index.getFields().size(); if (propCount != propValues.size()) { throw new IllegalArgumentException("Number of fields differs from the number of property values"); } getUniqueKeyIndex(txn, index).delete(txn.getEnvironmentTransaction(), propertyTypes.dataArrayToEntry(propValues.toArray(new Comparable[propCount]))); } @NotNull PersistentSequence getEntitiesSequence(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { synchronized (entitiesSequences) { PersistentSequence result = entitiesSequences.get(entityTypeId); if (result == null) { result = getSequence(txn, namingRulez.getEntitiesSequenceName(entityTypeId)); entitiesSequences.put(entityTypeId, result); } return result; } } void preloadTables(final PersistentStoreTransaction txn) { // preload tables for (final String entityType : getEntityTypes(txn)) { final int id = getEntityTypeId(txn, entityType, false); if (id == -1) { throw new IllegalStateException("Entity types iterator returned non-existent id"); } preloadTables(txn, id); } } private void preloadTables(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { getEntitiesTable(txn, entityTypeId); getPropertiesTable(txn, entityTypeId); getLinksTable(txn, entityTypeId); getBlobsTable(txn, entityTypeId); getEntitiesHistoryTable(txn, entityTypeId); getPropertiesHistoryTable(txn, entityTypeId); getLinksHistoryTable(txn, entityTypeId); getBlobsHistoryTable(txn, entityTypeId); } public void trackTableCreation(@NotNull final Store table, @NotNull final PersistentStoreTransaction txn) { if (table.isNew(txn.getEnvironmentTransaction())) { synchronized (tableCreationLog) { tableCreationLog.add(new TableCreationOperation() { @Override void persist(final Transaction txn) { table.persistCreation(txn); } }); } } } @Override public void logOperations(final Transaction txn, final FlushLog flushLog) { for (final PersistentSequence sequence : getAllSequences()) { sequence.logOperations(txn, flushLog); } entityTypes.logOperations(txn, flushLog); propertyIds.logOperations(txn, flushLog); linkIds.logOperations(txn, flushLog); for (final TableCreationOperation op : tableCreationLog) { op.persist(txn); flushLog.add(op); } } @NotNull public TwoColumnTable getEntityTypesTable() { return entityTypes.getTable(); } @NotNull public PropertyTypes getPropertyTypes() { return propertyTypes; } @NotNull public Store getEntitiesTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return ((SingleColumnTable) entitiesTables.get(txn, entityTypeId)).getDatabase(); } @NotNull public Store getEntitiesHistoryTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return ((SingleColumnTable) entitiesHistoryTables.get(txn, entityTypeId)).getDatabase(); } @NotNull public PropertiesTable getPropertiesTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return (PropertiesTable) propertiesTables.get(txn, entityTypeId); } @NotNull public Store getPropertiesHistoryTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return ((SingleColumnTable) propertiesHistoryTables.get(txn, entityTypeId)).getDatabase(); } @NotNull public TwoColumnTable getLinksTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return (TwoColumnTable) linksTables.get(txn, entityTypeId); } @NotNull public Store getLinksHistoryTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return ((SingleColumnTable) linksHistoryTables.get(txn, entityTypeId)).getDatabase(); } @NotNull public BlobsTable getBlobsTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return (BlobsTable) blobsTables.get(txn, entityTypeId); } @NotNull public Store getBlobsHistoryTable(@NotNull final PersistentStoreTransaction txn, final int entityTypeId) { return ((SingleColumnTable) blobsHistoryTables.get(txn, entityTypeId)).getDatabase(); } @Override public long getBlobsSize() { return blobVault.size(); } @NotNull public synchronized Store getUniqueKeyIndex(@NotNull final PersistentStoreTransaction txn, @NotNull final Index index) { return environment.openStore(getUniqueKeyIndexName(index), StoreConfig.WITHOUT_DUPLICATES_WITH_PREFIXING, txn.getEnvironmentTransaction()); } @Override @NotNull public EntityStoreSharedAsyncProcessor getAsyncProcessor() { return iterableCache.processor; } @Override public void close() { log.info("Closing..."); if (configMBean != null) { configMBean.unregister(); } try { getAsyncProcessor().finish(); synchronized (this) { blobVault.close(); environment.close(); } log.info("Closed successfully."); } catch (Exception e) { log.error("close() failed", e); throw ExodusException.toExodusException(e); } } @Override public BackupStrategy getBackupStrategy() { return new PersistentEntityStoreBackupStrategy(this); } @Override public long getTransactionCount() { return transactionCount; } // this method is synchronized outside void incTransactionCount() { ++transactionCount; } @Override public double getTransactionsPerSecond() { return (double) transactionCount * 1000.0f / (double) (System.currentTimeMillis() - startedAt); } @NotNull StoreNamingRules getNamingRules() { return namingRulez; } static boolean isEmptyOrInPlaceBlobHandle(final long blobHandle) { return EMPTY_BLOB_HANDLE == blobHandle || IN_PLACE_BLOB_HANDLE == blobHandle; } private String getUniqueKeyIndexName(@NotNull final Index index) { final List<IndexField> fields = index.getFields(); final int fieldCount = fields.size(); if (fieldCount < 1) { throw new EntityStoreException("Can't define unique key on empty set of fields"); } final LinkedHashMap<String, Boolean> names = new LinkedHashMap<>(); for (final IndexField field : fields) { final String name = field.getName(); final Boolean b = names.get(name); if (b != null && b == field.isProperty()) { throw new EntityStoreException("Can't define unique key, field is used twice: " + name); } names.put(name, field.isProperty()); } return namingRulez.getUniqueKeyIndexName(index.getOwnerEntityType(), names); } private void safeTruncateStore(@NotNull final PersistentStoreTransaction txn, @NotNull final String dbName) { environment.truncateStore(dbName, txn.getEnvironmentTransaction()); } private void deleteObsoleteBlobHandle(final long blobHandle, final PersistentStoreTransaction txn) { if (isEmptyOrInPlaceBlobHandle(blobHandle)) { return; } if (!txn.isBlobPreserved(blobHandle)) { txn.deleteBlob(blobHandle); txn.deferBlobDeletion(blobHandle); } } private interface DataGetter { Store getHistory(@NotNull PersistentStoreTransaction txn, int typeId); ByteIterable getUpToDateEntry(@NotNull PersistentStoreTransaction txn, int typeId, PropertyKey key); } private class PropertyDataGetter implements DataGetter { @Override public Store getHistory(@NotNull final PersistentStoreTransaction txn, int typeId) { return getPropertiesHistoryTable(txn, typeId); } @Override public ByteIterable getUpToDateEntry(@NotNull final PersistentStoreTransaction txn, int typeId, PropertyKey key) { return getPropertiesTable(txn, typeId).get(txn, PropertyKey.propertyKeyToEntry(key)); } } private class LinkDataGetter implements DataGetter { @Override public Store getHistory(@NotNull final PersistentStoreTransaction txn, int typeId) { return getLinksHistoryTable(txn, typeId); } @Override public ByteIterable getUpToDateEntry(@NotNull final PersistentStoreTransaction txn, int typeId, PropertyKey key) { return getLinksTable(txn, typeId).get(txn.getEnvironmentTransaction(), PropertyKey.propertyKeyToEntry(key)); } } private class DebugLinkDataGetter implements DataGetter { @Override public Store getHistory(@NotNull final PersistentStoreTransaction txn, int typeId) { return getLinksHistoryTable(txn, typeId); } @Override public ByteIterable getUpToDateEntry(@NotNull final PersistentStoreTransaction txn, int typeId, PropertyKey key) { final ByteIterable keyEntry = PropertyKey.propertyKeyToEntry(key); final ByteIterable valueEntry; try (Cursor cursor = getLinksTable(txn, typeId).getFirstIndexCursor(txn.getEnvironmentTransaction())) { valueEntry = cursor.getSearchKey(keyEntry); if (valueEntry == null) { return null; } if (cursor.getNextDup()) { // getNextDup screws up valueEntry only if dup is found throw new IllegalStateException("Only one link is allowed."); } } return valueEntry; } } private class BlobDataGetter implements DataGetter { @Override public Store getHistory(@NotNull final PersistentStoreTransaction txn, int typeId) { return getBlobsHistoryTable(txn, typeId); } @Override public ByteIterable getUpToDateEntry(@NotNull final PersistentStoreTransaction txn, int typeId, PropertyKey key) { return getBlobsTable(txn, typeId).get(txn.getEnvironmentTransaction(), key); } } private abstract class TableCreationOperation implements FlushLog.Operation { abstract void persist(final Transaction txn); @Override public void flushed() { synchronized (PersistentEntityStoreImpl.this.tableCreationLog) { tableCreationLog.remove(this); } } } }