diff --git a/engine/src/main/java/com/arcadedb/database/Database.java b/engine/src/main/java/com/arcadedb/database/Database.java index 7d9ef67995..b9723bb676 100644 --- a/engine/src/main/java/com/arcadedb/database/Database.java +++ b/engine/src/main/java/com/arcadedb/database/Database.java @@ -28,6 +28,7 @@ import com.arcadedb.graph.Vertex; import com.arcadedb.index.IndexCursor; import com.arcadedb.query.QueryEngine; +import com.arcadedb.query.select.Select; import com.arcadedb.query.sql.executor.ResultSet; import com.arcadedb.schema.Schema; @@ -55,6 +56,8 @@ enum TRANSACTION_ISOLATION_LEVEL { */ String getCurrentUserName(); + Select select(); + /** * Executes a command by specifying the language and arguments in a map. * @@ -218,9 +221,9 @@ enum TRANSACTION_ISOLATION_LEVEL { * @see DatabaseAsyncExecutor#newEdgeByKeys(String, String, Object, String, String, Object, boolean, String, boolean, boolean, NewEdgeCallback, Object...) * @see #newEdgeByKeys(Vertex, String, String[], Object[], boolean, String, boolean, Object...) */ - Edge newEdgeByKeys(String sourceVertexType, String[] sourceVertexKeyNames, Object[] sourceVertexKeyValues, String destinationVertexType, - String[] destinationVertexKeyNames, Object[] destinationVertexKeyValues, boolean createVertexIfNotExist, String edgeType, boolean bidirectional, - Object... properties); + Edge newEdgeByKeys(String sourceVertexType, String[] sourceVertexKeyNames, Object[] sourceVertexKeyValues, + String destinationVertexType, String[] destinationVertexKeyNames, Object[] destinationVertexKeyValues, + boolean createVertexIfNotExist, String edgeType, boolean bidirectional, Object... properties); /** * Creates a new edge between two vertices specifying the source vertex instance and the key/value pairs to lookup for the destination vertices. The direction @@ -242,8 +245,9 @@ Edge newEdgeByKeys(String sourceVertexType, String[] sourceVertexKeyNames, Objec * @see DatabaseAsyncExecutor#newEdgeByKeys(String, String, Object, String, String, Object, boolean, String, boolean, boolean, NewEdgeCallback, Object...) * @see #newEdgeByKeys(String, String[], Object[], String, String[], Object[], boolean, String, boolean, Object...) */ - Edge newEdgeByKeys(Vertex sourceVertex, String destinationVertexType, String[] destinationVertexKeyNames, Object[] destinationVertexKeyValues, - boolean createVertexIfNotExist, String edgeType, boolean bidirectional, Object... properties); + Edge newEdgeByKeys(Vertex sourceVertex, String destinationVertexType, String[] destinationVertexKeyNames, + Object[] destinationVertexKeyValues, boolean createVertexIfNotExist, String edgeType, boolean bidirectional, + Object... properties); /** * Returns the query engine by language name. diff --git a/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java b/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java index 5c5b7f8931..d4a7efb70c 100644 --- a/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java +++ b/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java @@ -59,6 +59,7 @@ import com.arcadedb.log.LogManager; import com.arcadedb.query.QueryEngine; import com.arcadedb.query.QueryEngineManager; +import com.arcadedb.query.select.Select; import com.arcadedb.query.sql.executor.ResultSet; import com.arcadedb.query.sql.parser.ExecutionPlanCache; import com.arcadedb.query.sql.parser.StatementCache; @@ -87,9 +88,9 @@ public class EmbeddedDatabase extends RWLockContext implements DatabaseInternal { public static final int EDGE_LIST_INITIAL_CHUNK_SIZE = 64; public static final int MAX_RECOMMENDED_EDGE_LIST_CHUNK_SIZE = 8192; - private static final Set SUPPORTED_FILE_EXT = Set.of(Dictionary.DICT_EXT, Bucket.BUCKET_EXT, - LSMTreeIndexMutable.NOTUNIQUE_INDEX_EXT, LSMTreeIndexMutable.UNIQUE_INDEX_EXT, LSMTreeIndexCompacted.NOTUNIQUE_INDEX_EXT, - LSMTreeIndexCompacted.UNIQUE_INDEX_EXT, HnswVectorIndex.FILE_EXT); + private static final Set SUPPORTED_FILE_EXT = Set.of(Dictionary.DICT_EXT, + Bucket.BUCKET_EXT, LSMTreeIndexMutable.NOTUNIQUE_INDEX_EXT, LSMTreeIndexMutable.UNIQUE_INDEX_EXT, + LSMTreeIndexCompacted.NOTUNIQUE_INDEX_EXT, LSMTreeIndexCompacted.UNIQUE_INDEX_EXT, HnswVectorIndex.FILE_EXT); public final AtomicLong indexCompactions = new AtomicLong(); protected final String name; protected final ComponentFile.MODE mode; @@ -127,8 +128,8 @@ public class EmbeddedDatabase extends RWLockContext implements DatabaseInternal private final ConcurrentHashMap reusableQueryEngines = new ConcurrentHashMap<>(); private TRANSACTION_ISOLATION_LEVEL transactionIsolationLevel = TRANSACTION_ISOLATION_LEVEL.READ_COMMITTED; - protected EmbeddedDatabase(final String path, final ComponentFile.MODE mode, final ContextConfiguration configuration, final SecurityManager security, - final Map>> callbacks) { + protected EmbeddedDatabase(final String path, final ComponentFile.MODE mode, final ContextConfiguration configuration, + final SecurityManager security, final Map>> callbacks) { try { this.mode = mode; this.configuration = configuration; @@ -137,7 +138,8 @@ protected EmbeddedDatabase(final String path, final ComponentFile.MODE mode, fin this.serializer = new BinarySerializer(configuration); this.walFactory = mode == ComponentFile.MODE.READ_WRITE ? new WALFileFactoryEmbedded() : null; this.statementCache = new StatementCache(this, configuration.getValueAsInteger(GlobalConfiguration.SQL_STATEMENT_CACHE)); - this.executionPlanCache = new ExecutionPlanCache(this, configuration.getValueAsInteger(GlobalConfiguration.SQL_STATEMENT_CACHE)); + this.executionPlanCache = new ExecutionPlanCache(this, + configuration.getValueAsInteger(GlobalConfiguration.SQL_STATEMENT_CACHE)); if (path.endsWith(File.separator)) databasePath = path.substring(0, path.length() - 1); @@ -185,7 +187,8 @@ protected void open() { protected void create() { final File databaseDirectory = new File(databasePath); - if (new File(databaseDirectory, EmbeddedSchema.SCHEMA_FILE_NAME).exists() || new File(databaseDirectory, EmbeddedSchema.SCHEMA_PREV_FILE_NAME).exists()) + if (new File(databaseDirectory, EmbeddedSchema.SCHEMA_FILE_NAME).exists() || new File(databaseDirectory, + EmbeddedSchema.SCHEMA_PREV_FILE_NAME).exists()) throw new DatabaseOperationException("Database '" + databasePath + "' already exists"); if (!databaseDirectory.exists() && !databaseDirectory.mkdirs()) @@ -371,7 +374,8 @@ public void commit() { executeInReadLock(() -> { checkTransactionIsActive(false); - final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext(EmbeddedDatabase.this.getDatabasePath()); + final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext( + EmbeddedDatabase.this.getDatabasePath()); try { current.getLastTransaction().commit(); } finally { @@ -390,7 +394,8 @@ public void rollback() { try { checkTransactionIsActive(false); - final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext(EmbeddedDatabase.this.getDatabasePath()); + final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext( + EmbeddedDatabase.this.getDatabasePath()); current.popIfNotLastTransaction().rollback(); } catch (final TransactionException e) { @@ -408,7 +413,8 @@ public void rollbackAllNested() { stats.txRollbacks.incrementAndGet(); executeInReadLock(() -> { - final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext(EmbeddedDatabase.this.getDatabasePath()); + final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext( + EmbeddedDatabase.this.getDatabasePath()); TransactionContext tx; while ((tx = current.popIfNotLastTransaction()) != null) { @@ -455,7 +461,8 @@ public void scanType(final String typeName, final boolean polymorphic, final Doc } @Override - public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, final ErrorRecordCallback errorRecordCallback) { + public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, + final ErrorRecordCallback errorRecordCallback) { stats.scanType.incrementAndGet(); executeInReadLock(() -> { @@ -523,7 +530,7 @@ public Iterator iterateType(final String typeName, final boolean polymor // SET THE PROFILED LIMITS IF ANY iter.setLimit(getResultSetLimit()); - iter.setTimeout(getReadTimeout()); + iter.setTimeout(getReadTimeout(), true); for (final Bucket b : type.getBuckets(polymorphic)) iter.addIterator(b.iterator()); @@ -690,7 +697,8 @@ public IndexCursor lookupByKey(final String type, final String[] keyNames, final final TypeIndex idx = t.getPolymorphicIndexByProperties(keyNames); if (idx == null) - throw new IllegalArgumentException("No index has been created on type '" + type + "' properties " + Arrays.toString(keyNames)); + throw new IllegalArgumentException( + "No index has been created on type '" + type + "' properties " + Arrays.toString(keyNames)); return idx.get(keyValues); }); @@ -902,10 +910,11 @@ public void updateRecord(final Record record) { public Document getOriginalDocument(final Record record) { final Binary originalBuffer = ((RecordInternal) record).getBuffer(); if (originalBuffer == null) - throw new IllegalStateException( - "Cannot read original buffer for record " + record.getIdentity() + ". In case of tx retry check the record is created inside the transaction"); + throw new IllegalStateException("Cannot read original buffer for record " + record.getIdentity() + + ". In case of tx retry check the record is created inside the transaction"); originalBuffer.rewind(); - return (Document) recordFactory.newImmutableRecord(this, ((Document) record).getType(), record.getIdentity(), originalBuffer, null); + return (Document) recordFactory.newImmutableRecord(this, ((Document) record).getType(), record.getIdentity(), originalBuffer, + null); } @Override @@ -914,7 +923,9 @@ public void updateRecordNoLock(final Record record, final boolean discardRecordA final boolean implicitTransaction = checkTransactionIsActive(autoTransaction); try { - final List indexes = record instanceof Document ? indexer.getInvolvedIndexes((Document) record) : Collections.emptyList(); + final List indexes = record instanceof Document ? + indexer.getInvolvedIndexes((Document) record) : + Collections.emptyList(); if (!indexes.isEmpty()) { // UPDATE THE INDEXES TOO @@ -1023,7 +1034,8 @@ public boolean transaction(final TransactionScope txBlock, final boolean joinCur } @Override - public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, int attempts, final OkCallback ok, final ErrorCallback error) { + public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, int attempts, final OkCallback ok, + final ErrorCallback error) { if (txBlock == null) throw new IllegalArgumentException("Transaction block is null"); @@ -1127,7 +1139,8 @@ public MutableEmbeddedDocument newEmbeddedDocument(final EmbeddedModifier modifi final DocumentType type = schema.getType(typeName); if (!type.getClass().equals(DocumentType.class)) throw new IllegalArgumentException( - "Cannot create an embedded document of type '" + typeName + "' because it is a " + type.getClass().getName() + " instead of a document type "); + "Cannot create an embedded document of type '" + typeName + "' because it is a " + type.getClass().getName() + + " instead of a document type "); return new MutableEmbeddedDocument(wrappedDatabaseInstance, type, modifier); } @@ -1146,9 +1159,10 @@ public MutableVertex newVertex(final String typeName) { return new MutableVertex(wrappedDatabaseInstance, (VertexType) type, null); } - public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, final Object[] sourceVertexKeyValues, - final String destinationVertexType, final String[] destinationVertexKeyNames, final Object[] destinationVertexKeyValues, - final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, final Object... properties) { + public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, + final Object[] sourceVertexKeyValues, final String destinationVertexType, final String[] destinationVertexKeyNames, + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { if (sourceVertexKeyNames == null) throw new IllegalArgumentException("Source vertex key is null"); @@ -1172,11 +1186,13 @@ public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVe ((MutableVertex) sourceVertex).save(); } else throw new IllegalArgumentException( - "Cannot find source vertex with key " + Arrays.toString(sourceVertexKeyNames) + "=" + Arrays.toString(sourceVertexKeyValues)); + "Cannot find source vertex with key " + Arrays.toString(sourceVertexKeyNames) + "=" + Arrays.toString( + sourceVertexKeyValues)); } else sourceVertex = v1Result.next().getIdentity().asVertex(); - final Iterator v2Result = lookupByKey(destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues); + final Iterator v2Result = lookupByKey(destinationVertexType, destinationVertexKeyNames, + destinationVertexKeyValues); final Vertex destinationVertex; if (!v2Result.hasNext()) { if (createVertexIfNotExist) { @@ -1186,7 +1202,8 @@ public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVe ((MutableVertex) destinationVertex).save(); } else throw new IllegalArgumentException( - "Cannot find destination vertex with key " + Arrays.toString(destinationVertexKeyNames) + "=" + Arrays.toString(destinationVertexKeyValues)); + "Cannot find destination vertex with key " + Arrays.toString(destinationVertexKeyNames) + "=" + Arrays.toString( + destinationVertexKeyValues)); } else destinationVertex = v2Result.next().getIdentity().asVertex(); @@ -1196,8 +1213,8 @@ public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVe } public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVertexType, final String[] destinationVertexKeyNames, - final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, - final Object... properties) { + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { if (sourceVertex == null) throw new IllegalArgumentException("Source vertex is null"); @@ -1207,7 +1224,8 @@ public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVer if (destinationVertexKeyNames.length != destinationVertexKeyValues.length) throw new IllegalArgumentException("Destination vertex key and value arrays have different sizes"); - final Iterator v2Result = lookupByKey(destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues); + final Iterator v2Result = lookupByKey(destinationVertexType, destinationVertexKeyNames, + destinationVertexKeyValues); final Vertex destinationVertex; if (!v2Result.hasNext()) { if (createVertexIfNotExist) { @@ -1217,7 +1235,8 @@ public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVer ((MutableVertex) destinationVertex).save(); } else throw new IllegalArgumentException( - "Cannot find destination vertex with key " + Arrays.toString(destinationVertexKeyNames) + "=" + Arrays.toString(destinationVertexKeyValues)); + "Cannot find destination vertex with key " + Arrays.toString(destinationVertexKeyNames) + "=" + Arrays.toString( + destinationVertexKeyValues)); } else destinationVertex = v2Result.next().getIdentity().asVertex(); @@ -1295,7 +1314,8 @@ public ResultSet command(final String language, final String query, final Object } @Override - public ResultSet command(final String language, final String query, final ContextConfiguration configuration, final Object... parameters) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Object... parameters) { checkDatabaseIsOpen(); stats.commands.incrementAndGet(); return getQueryEngine(language).command(query, configuration, parameters); @@ -1307,7 +1327,8 @@ public ResultSet command(final String language, final String query, final Map parameters) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Map parameters) { checkDatabaseIsOpen(); stats.commands.incrementAndGet(); return getQueryEngine(language).command(query, configuration, parameters); @@ -1343,6 +1364,11 @@ public ResultSet query(final String language, final String query, final Map 0) { LogManager.instance() - .log(this, Level.FINE, "Wait %d ms before the next retry for transaction commit (threadId=%d)", retryDelay, Thread.currentThread().getId()); + .log(this, Level.FINE, "Wait %d ms before the next retry for transaction commit (threadId=%d)", retryDelay, + Thread.currentThread().getId()); try { Thread.sleep(1 + new Random().nextInt(retryDelay)); diff --git a/engine/src/main/java/com/arcadedb/database/IndexCursorCollection.java b/engine/src/main/java/com/arcadedb/database/IndexCursorCollection.java index 595c6e7935..e6dd6a28c2 100644 --- a/engine/src/main/java/com/arcadedb/database/IndexCursorCollection.java +++ b/engine/src/main/java/com/arcadedb/database/IndexCursorCollection.java @@ -43,11 +43,6 @@ public Identifiable getRecord() { return last; } - @Override - public int getScore() { - return 0; - } - @Override public BinaryComparator getComparator() { return null; diff --git a/engine/src/main/java/com/arcadedb/index/EmptyIndexCursor.java b/engine/src/main/java/com/arcadedb/index/EmptyIndexCursor.java index 289e1f323c..3d08fd62b3 100644 --- a/engine/src/main/java/com/arcadedb/index/EmptyIndexCursor.java +++ b/engine/src/main/java/com/arcadedb/index/EmptyIndexCursor.java @@ -45,11 +45,6 @@ public Identifiable next() { throw new NoSuchElementException(); } - @Override - public int getScore() { - return 0; - } - @Override public BinaryComparator getComparator() { return null; @@ -62,7 +57,7 @@ public byte[] getBinaryKeyTypes() { @Override public long estimateSize() { - return 0l; + return 0L; } @Override diff --git a/engine/src/main/java/com/arcadedb/index/IndexCursor.java b/engine/src/main/java/com/arcadedb/index/IndexCursor.java index 2b49a32dcf..847a5c0bbc 100644 --- a/engine/src/main/java/com/arcadedb/index/IndexCursor.java +++ b/engine/src/main/java/com/arcadedb/index/IndexCursor.java @@ -32,8 +32,6 @@ public interface IndexCursor extends Cursor { Identifiable getRecord(); - int getScore(); - default void close() { // NO ACTIONS } diff --git a/engine/src/main/java/com/arcadedb/index/MultiIndexCursor.java b/engine/src/main/java/com/arcadedb/index/MultiIndexCursor.java index 8c5524cb46..28aad217f8 100644 --- a/engine/src/main/java/com/arcadedb/index/MultiIndexCursor.java +++ b/engine/src/main/java/com/arcadedb/index/MultiIndexCursor.java @@ -56,8 +56,8 @@ public MultiIndexCursor(final List indexes, final boolean ascendi initCursors(); } - public MultiIndexCursor(final List indexes, final Object[] fromKeys, final boolean ascendingOrder, final boolean includeFrom, - final int limit) { + public MultiIndexCursor(final List indexes, final Object[] fromKeys, final boolean ascendingOrder, + final boolean includeFrom, final int limit) { this.cursors = new ArrayList<>(indexes.size()); this.limit = limit; for (final Index i : indexes) { @@ -148,11 +148,6 @@ public Identifiable next() { return nextValue; } - @Override - public int getScore() { - return -1; - } - @Override public void close() { for (final IndexCursor cursor : cursors) @@ -162,11 +157,18 @@ public void close() { @Override public long estimateSize() { long tot = 0L; - for (final IndexCursor cursor : cursors) + for (final IndexCursor cursor : cursors) { + if (cursor.estimateSize() == -1) + return -1; tot += cursor.estimateSize(); + } return tot; } + public int getCursors() { + return cursors.size(); + } + @Override public Iterator iterator() { return this; diff --git a/engine/src/main/java/com/arcadedb/index/TempIndexCursor.java b/engine/src/main/java/com/arcadedb/index/TempIndexCursor.java index 9597f8a1e2..9f9fb0f2b6 100644 --- a/engine/src/main/java/com/arcadedb/index/TempIndexCursor.java +++ b/engine/src/main/java/com/arcadedb/index/TempIndexCursor.java @@ -54,11 +54,6 @@ public Identifiable next() { return current.record; } - @Override - public int getScore() { - return current.score; - } - @Override public BinaryComparator getComparator() { return null; diff --git a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexCursor.java b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexCursor.java index dbacb07b5e..580a356383 100644 --- a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexCursor.java +++ b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexCursor.java @@ -59,8 +59,8 @@ public LSMTreeIndexCursor(final LSMTreeIndexMutable index, final boolean ascendi this(index, ascendingOrder, null, true, null, true); } - public LSMTreeIndexCursor(final LSMTreeIndexMutable index, final boolean ascendingOrder, final Object[] fromKeys, final boolean beginKeysInclusive, - final Object[] toKeys, final boolean endKeysInclusive) throws IOException { + public LSMTreeIndexCursor(final LSMTreeIndexMutable index, final boolean ascendingOrder, final Object[] fromKeys, + final boolean beginKeysInclusive, final Object[] toKeys, final boolean endKeysInclusive) throws IOException { this.index = index; this.ascendingOrder = ascendingOrder; this.binaryKeyTypes = index.getBinaryKeyTypes(); @@ -118,13 +118,14 @@ public LSMTreeIndexCursor(final LSMTreeIndexMutable index, final boolean ascendi if (serializedFromKeys != null) { // SEEK FOR THE FROM RANGE - final BasePage currentPage = index.getDatabase().getTransaction().getPage(new PageId(index.getFileId(), pageId), index.getPageSize()); + final BasePage currentPage = index.getDatabase().getTransaction() + .getPage(new PageId(index.getFileId(), pageId), index.getPageSize()); final Binary currentPageBuffer = new Binary(currentPage.slice()); final int count = index.getCount(currentPage); if (count > 0) { - final LSMTreeIndexMutable.LookupResult lookupResult = index.lookupInPage(currentPage.getPageId().getPageNumber(), count, currentPageBuffer, - serializedFromKeys, ascendingOrder ? 2 : 3); + final LSMTreeIndexMutable.LookupResult lookupResult = index.lookupInPage(currentPage.getPageId().getPageNumber(), count, + currentPageBuffer, serializedFromKeys, ascendingOrder ? 2 : 3); if (!lookupResult.outside) { pageCursors[cursorIdx] = index.newPageIterator(pageId, lookupResult.keyIndex, ascendingOrder); @@ -148,7 +149,8 @@ public LSMTreeIndexCursor(final LSMTreeIndexMutable index, final boolean ascendi if (ascendingOrder) { pageCursors[cursorIdx] = index.newPageIterator(pageId, -1, true); } else { - final BasePage currentPage = index.getDatabase().getTransaction().getPage(new PageId(index.getFileId(), pageId), index.getPageSize()); + final BasePage currentPage = index.getDatabase().getTransaction() + .getPage(new PageId(index.getFileId(), pageId), index.getPageSize()); pageCursors[cursorIdx] = index.newPageIterator(pageId, index.getCount(currentPage), false); } @@ -249,9 +251,11 @@ public String dumpStats() { if (cursor == null) buffer.append(String.format("%n- Cursor[%d] = null", i)); else { - buffer.append(String.format("%n- Cursor[%d] %s=%s index=%s compacted=%s totalKeys=%d ascending=%s keyTypes=%s currentPageId=%s currentPosInPage=%d", i, - Arrays.toString(cursorKeys[i]), Arrays.toString(cursor.getValue()), cursor.index, cursor instanceof LSMTreeIndexUnderlyingCompactedSeriesCursor, - cursor.totalKeys, cursor.ascendingOrder, Arrays.toString(cursor.keyTypes), cursor.getCurrentPageId(), cursor.getCurrentPositionInPage())); + buffer.append(String.format( + "%n- Cursor[%d] %s=%s index=%s compacted=%s totalKeys=%d ascending=%s keyTypes=%s currentPageId=%s currentPosInPage=%d", + i, Arrays.toString(cursorKeys[i]), Arrays.toString(cursor.getValue()), cursor.index, + cursor instanceof LSMTreeIndexUnderlyingCompactedSeriesCursor, cursor.totalKeys, cursor.ascendingOrder, + Arrays.toString(cursor.keyTypes), cursor.getCurrentPageId(), cursor.getCurrentPositionInPage())); } } @@ -391,8 +395,8 @@ else if (tempCurrentValues.length > 0) { if (serializedToKeys != null) { final int compare = LSMTreeIndexMutable.compareKeys(comparator, binaryKeyTypes, cursorKeys[minorKeyIndex], toKeys); - if ((ascendingOrder && ((toKeysInclusive && compare > 0) || (!toKeysInclusive && compare >= 0))) || (!ascendingOrder && ( - (toKeysInclusive && compare < 0) || (!toKeysInclusive && compare <= 0)))) { + if ((ascendingOrder && ((toKeysInclusive && compare > 0) || (!toKeysInclusive && compare >= 0))) || (!ascendingOrder + && ((toKeysInclusive && compare < 0) || (!toKeysInclusive && compare <= 0)))) { currentCursor.close(); pageCursors[minorKeyIndex] = null; cursorKeys[minorKeyIndex] = null; @@ -420,8 +424,9 @@ else if (tempCurrentValues.length > 0) { if (txCursor == null || !txCursor.hasNext()) getClosestEntryInTx(currentKeys != null ? currentKeys : fromKeys, false); - } while ((currentValues == null || currentValues.length == 0 || (currentValueIndex < currentValues.length && index.isDeletedEntry( - currentValues[currentValueIndex]))) && hasNext()); + } while ( + (currentValues == null || currentValues.length == 0 || (currentValueIndex < currentValues.length && index.isDeletedEntry( + currentValues[currentValueIndex]))) && hasNext()); return currentValues == null || currentValueIndex >= currentValues.length ? null : currentValues[currentValueIndex++]; } @@ -451,7 +456,8 @@ else if (inclusive) entry = indexChanges.lowerEntry(new TransactionIndexContext.ComparableKey(keys)); } - final Map values = entry != null ? entry.getValue() : null; + final Map values = + entry != null ? entry.getValue() : null; if (values != null) { for (final TransactionIndexContext.IndexKey value : values.values()) { if (value != null) { @@ -502,11 +508,6 @@ public Identifiable getRecord() { return null; } - @Override - public int getScore() { - return 1; - } - @Override public void close() { for (final LSMTreeIndexUnderlyingAbstractCursor it : pageCursors) diff --git a/engine/src/main/java/com/arcadedb/query/select/Select.java b/engine/src/main/java/com/arcadedb/query/select/Select.java new file mode 100644 index 0000000000..98f6c9b1ba --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/Select.java @@ -0,0 +1,318 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.DatabaseInternal; +import com.arcadedb.database.Document; +import com.arcadedb.engine.Bucket; +import com.arcadedb.graph.Edge; +import com.arcadedb.graph.Vertex; +import com.arcadedb.schema.DocumentType; +import com.arcadedb.serializer.json.JSONArray; +import com.arcadedb.serializer.json.JSONObject; +import com.arcadedb.utility.Pair; + +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.*; + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class Select { + final DatabaseInternal database; + + enum STATE {DEFAULT, WHERE, COMPILED} + + Map parameters; + SelectTreeNode rootTreeElement; + DocumentType fromType; + List fromBuckets; + SelectOperator operator; + SelectRuntimeValue property; + Object propertyValue; + boolean polymorphic = true; + int limit = -1; + int skip = 0; + long timeoutInMs = 0; + boolean exceptionOnTimeout; + ArrayList> orderBy; + + STATE state = STATE.DEFAULT; + private SelectTreeNode lastTreeElement; + + public Select(final DatabaseInternal database) { + this.database = database; + } + + public Select fromType(final String fromType) { + checkNotCompiled(); + if (this.fromType != null) + throw new IllegalArgumentException("From type has already been set"); + if (this.fromBuckets != null) + throw new IllegalArgumentException("From bucket(s) has already been set"); + + this.fromType = database.getSchema().getType(fromType); + return this; + } + + public Select fromBuckets(final String... fromBucketNames) { + checkNotCompiled(); + if (this.fromType != null) + throw new IllegalArgumentException("From type has already been set"); + if (this.fromBuckets != null) + throw new IllegalArgumentException("From bucket(s) has already been set"); + + this.fromBuckets = Arrays.stream(fromBucketNames).map(b -> database.getSchema().getBucketByName(b)) + .collect(Collectors.toList()); + return this; + } + + public Select fromBuckets(final Integer... fromBucketIds) { + checkNotCompiled(); + if (this.fromType != null) + throw new IllegalArgumentException("From type has already been set"); + if (this.fromBuckets != null) + throw new IllegalArgumentException("From bucket(s) has already been set"); + + this.fromBuckets = Arrays.stream(fromBucketIds).map(b -> database.getSchema().getBucketById(b)).collect(Collectors.toList()); + return this; + } + + public Select property(final String name) { + checkNotCompiled(); + if (property != null) + throw new IllegalArgumentException("Property has already been set"); + if (state != STATE.WHERE) + throw new IllegalArgumentException("No context was provided for the parameter"); + this.property = new SelectPropertyValue(name); + return this; + } + + public Select value(final Object value) { + checkNotCompiled(); + if (property == null) + throw new IllegalArgumentException("Property has not been set"); + + switch (state) { + case WHERE: + if (operator == null) + throw new IllegalArgumentException("No operator has been set"); + if (propertyValue != null) + throw new IllegalArgumentException("Property value has already been set"); + this.propertyValue = value; + break; + } + + return this; + } + + public SelectWhereLeftBlock where() { + checkNotCompiled(); + if (rootTreeElement != null) + throw new IllegalArgumentException("Where has already been set"); + state = STATE.WHERE; + return new SelectWhereLeftBlock(this); + } + + public Select parameter(final String parameterName) { + checkNotCompiled(); + this.propertyValue = new SelectParameterValue(this, parameterName); + return this; + } + + public Select parameter(final String paramName, final Object paramValue) { + if (parameters == null) + parameters = new HashMap<>(); + parameters.put(paramName, paramValue); + return this; + } + + public Select limit(final int limit) { + checkNotCompiled(); + this.limit = limit; + return this; + } + + public Select skip(final int skip) { + checkNotCompiled(); + this.skip = skip; + return this; + } + + public Select timeout(final long timeoutValue, final TimeUnit timeoutUnit, final boolean exceptionOnTimeout) { + checkNotCompiled(); + this.timeoutInMs = timeoutUnit.toMillis(timeoutValue); + this.exceptionOnTimeout = exceptionOnTimeout; + return this; + } + + public Select polymorphic(final boolean polymorphic) { + checkNotCompiled(); + if (fromType == null) + throw new IllegalArgumentException("FromType was not set"); + this.polymorphic = polymorphic; + return this; + } + + public Select orderBy(final String property, final boolean ascending) { + checkNotCompiled(); + if (this.orderBy == null) + this.orderBy = new ArrayList<>(); + this.orderBy.add(new Pair<>(property, ascending)); + return this; + } + + public Select json(final JSONObject json) { + checkNotCompiled(); + if (json.has("fromType")) { + fromType(json.getString("fromType")); + if (json.has("polymorphic")) + polymorphic(json.getBoolean("polymorphic")); + } else if (json.has("fromBuckets")) { + final JSONArray buckets = json.getJSONArray("fromBuckets"); + fromBuckets( + buckets.toList().stream().map(b -> database.getSchema().getBucketByName(b.toString())).collect(Collectors.toList()) + .toArray(new String[buckets.length()])); + } + + if (json.has("where")) { + where(); + parseJsonCondition(json.getJSONArray("where")); + } + + if (json.has("limit")) + limit(json.getInt("limit")); + if (json.has("skip")) + skip(json.getInt("skip")); + + return this; + } + + private void parseJsonCondition(final JSONArray condition) { + if (condition.length() != 3) + throw new IllegalArgumentException("Invalid condition " + condition); + + final Object parsedLeft = condition.get(0); + if (parsedLeft instanceof JSONArray) + parseJsonCondition((JSONArray) parsedLeft); + else if (parsedLeft instanceof String && ((String) parsedLeft).startsWith(":")) + property(((String) parsedLeft).substring(1)); + else if (parsedLeft instanceof String && ((String) parsedLeft).startsWith("#")) + parameter(((String) parsedLeft).substring(1)); + else + throw new IllegalArgumentException("Unsupported value " + parsedLeft); + + final SelectOperator parsedOperator = SelectOperator.byName(condition.getString(1)); + + if (parsedOperator.logicOperator) + setLogic(parsedOperator); + else + setOperator(parsedOperator); + + final Object parsedRight = condition.get(2); + if (parsedRight instanceof JSONArray) + parseJsonCondition((JSONArray) parsedRight); + else if (parsedRight instanceof String && ((String) parsedRight).startsWith(":")) + property(((String) parsedRight).substring(1)); + else if (parsedRight instanceof String && ((String) parsedRight).startsWith("#")) + parameter(((String) parsedRight).substring(1)); + else + value(parsedRight); + } + + public SelectCompiled compile() { + if (fromType == null && fromBuckets == null) + throw new IllegalArgumentException("from (type or buckets) has not been set"); + if (state == STATE.WHERE) { + setLogic(SelectOperator.run); + } + state = STATE.COMPILED; + return new SelectCompiled(this); + } + + public SelectIterator vertices() { + return run(); + } + + public SelectIterator edges() { + return run(); + } + + public SelectIterator documents() { + return run(); + } + + SelectIterator run() { + compile(); + return new SelectExecutor(this).execute(); + } + + SelectWhereLeftBlock setLogic(final SelectOperator newLogicOperator) { + checkNotCompiled(); + if (operator == null) + throw new IllegalArgumentException("Missing condition"); + + final SelectTreeNode newTreeElement = new SelectTreeNode(property, operator, propertyValue); + if (rootTreeElement == null) { + // 1ST TIME ONLY + rootTreeElement = new SelectTreeNode(newTreeElement, newLogicOperator, null); + newTreeElement.setParent(rootTreeElement); + lastTreeElement = newTreeElement; + } else { + if (newLogicOperator.equals(SelectOperator.run)) { + // EXECUTION = LAST NODE: APPEND TO THE RIGHT OF THE LATEST + lastTreeElement.getParent().setRight(newTreeElement); + } else if (lastTreeElement.getParent().operator.precedence < newLogicOperator.precedence) { + // AND+ OPERATOR + final SelectTreeNode newNode = new SelectTreeNode(newTreeElement, newLogicOperator, null); + lastTreeElement.getParent().setRight(newNode); + lastTreeElement = newTreeElement; + } else { + // OR+ OPERATOR + final SelectTreeNode currentParent = lastTreeElement.getParent(); + currentParent.setRight(newTreeElement); + final SelectTreeNode newNode = new SelectTreeNode(currentParent, newLogicOperator, null); + if (rootTreeElement.equals(currentParent)) + rootTreeElement = newNode; + else + newNode.setParent(currentParent.getParent()); + lastTreeElement = currentParent; + } + } + + operator = null; + property = null; + propertyValue = null; + return new SelectWhereLeftBlock(this); + } + + void checkNotCompiled() { + if (state == STATE.COMPILED) + throw new IllegalArgumentException("Cannot modify the structure of a select what has been already compiled"); + } + + SelectWhereRightBlock setOperator(final SelectOperator selectOperator) { + checkNotCompiled(); + if (operator != null) + throw new IllegalArgumentException("Operator has already been set (" + operator + ")"); + operator = selectOperator; + return new SelectWhereRightBlock(this); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectCompiled.java b/engine/src/main/java/com/arcadedb/query/select/SelectCompiled.java new file mode 100644 index 0000000000..fd79d75a3f --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectCompiled.java @@ -0,0 +1,82 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.graph.Edge; +import com.arcadedb.graph.Vertex; +import com.arcadedb.serializer.json.JSONObject; + +import java.util.*; +import java.util.stream.*; + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectCompiled { + private final Select select; + + public SelectCompiled(final Select select) { + this.select = select; + } + + public SelectCompiled parameter(final String paramName, final Object paramValue) { + if (select.parameters == null) + select.parameters = new HashMap<>(); + select.parameters.put(paramName, paramValue); + return this; + } + + public JSONObject json() { + final JSONObject json = new JSONObject(); + + if (select.fromType != null) { + json.put("fromType", select.fromType.getName()); + if (!select.polymorphic) + json.put("polymorphic", select.polymorphic); + } else if (select.fromBuckets != null) + json.put("fromBuckets", select.fromBuckets.stream().map(b -> b.getName()).collect(Collectors.toList())); + + if (select.rootTreeElement != null) + json.put("where", select.rootTreeElement.toJSON()); + + if (select.limit > -1) + json.put("limit", select.limit); + if (select.skip > -1) + json.put("skip", select.skip); + if (select.timeoutInMs > 0) { + json.put("timeoutInMs", select.timeoutInMs); + json.put("exceptionOnTimeout", select.exceptionOnTimeout); + } + + return json; + } + + public SelectIterator vertices() { + return select.vertices(); + } + + public SelectIterator edges() { + return select.edges(); + } + + public SelectIterator documents() { + return select.documents(); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectExecutor.java b/engine/src/main/java/com/arcadedb/query/select/SelectExecutor.java new file mode 100644 index 0000000000..86ff02cfe4 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectExecutor.java @@ -0,0 +1,229 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.database.Identifiable; +import com.arcadedb.engine.Bucket; +import com.arcadedb.index.Index; +import com.arcadedb.index.IndexCursor; +import com.arcadedb.index.MultiIndexCursor; +import com.arcadedb.index.TypeIndex; +import com.arcadedb.utility.MultiIterator; +import com.arcadedb.utility.Pair; + +import java.util.*; + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectExecutor { + final Select select; + long evaluatedRecords = 0; + List usedIndexes = null; + + class IndexInfo { + public final Index index; + public final String property; + public final boolean order; + + IndexInfo(final Index index, final String property, final boolean order) { + this.index = index; + this.property = property; + this.order = order; + } + } + + public SelectExecutor(final Select select) { + this.select = select; + } + + SelectIterator execute() { + final MultiIndexCursor iteratorFromIndexes = lookForIndexes(); + + final Iterator iterator; + + if (iteratorFromIndexes != null) + iterator = iteratorFromIndexes; + else if (select.fromType != null) + iterator = (MultiIterator) select.database.iterateType(select.fromType.getName(), select.polymorphic); + else if (select.fromBuckets.size() == 1) + iterator = (MultiIterator) select.database.iterateBucket(select.fromBuckets.get(0).getName()); + else { + final MultiIterator multiIterator = new MultiIterator<>(); + for (Bucket b : select.fromBuckets) + multiIterator.addIterator(b.iterator()); + iterator = multiIterator; + } + + if (select.timeoutInMs > 0 && iterator instanceof MultiIterator) + ((MultiIterator) iterator).setTimeout(select.timeoutInMs, select.exceptionOnTimeout); + + return new SelectIterator<>(this, iterator, iteratorFromIndexes != null && iteratorFromIndexes.getCursors() > 1); + } + + private MultiIndexCursor lookForIndexes() { + if (select.fromType != null && select.rootTreeElement != null) { + final List cursors = new ArrayList<>(); + + // FIND AVAILABLE INDEXES + boolean canUseIndexes = isTheNodeFullyIndexed(select.rootTreeElement); + + filterWithIndexes(select.rootTreeElement, cursors); + if (!cursors.isEmpty()) + return new MultiIndexCursor(cursors, select.limit, true); + } + return null; + } + + private void filterWithIndexes(final SelectTreeNode node, final List cursors) { + if (!(node.left instanceof SelectTreeNode)) + filterWithIndexesFinalNode(node, cursors); + else { + filterWithIndexes((SelectTreeNode) node.left, cursors); + if (node.right != null) + filterWithIndexes((SelectTreeNode) node.right, cursors); + } + } + + private void filterWithIndexesFinalNode(final SelectTreeNode node, final List cursors) { + if (node.index == null) + return; + + if (node.getParent().operator == SelectOperator.or) { + if (node != node.getParent().right &&// + !isTheNodeFullyIndexed((SelectTreeNode) node.getParent().right)) + // UNDER AN 'OR' OPERATOR BUT THE OTHER RIGHT VALUE IS NOT INDEXED: CANNOT USE THE CURRENT INDEX + return; + } + + final Object rightValue; + if (node.right instanceof SelectParameterValue) + rightValue = ((SelectParameterValue) node.right).eval(null); + else + rightValue = node.right; + + final String propertyName = ((SelectPropertyValue) node.left).propertyName; + + boolean ascendingOrder = true; // DEFAULT + + final IndexCursor cursor; + if (node.operator == SelectOperator.eq) + cursor = node.index.get(new Object[] { rightValue }); + else { + // RANGE + if (select.orderBy != null) + for (Pair entry : select.orderBy) { + if (propertyName.equals(entry.getFirst())) { + if (!entry.getSecond()) + ascendingOrder = false; + break; + } + } + + if (node.operator == SelectOperator.gt) + cursor = node.index.range(ascendingOrder, new Object[] { rightValue }, false, null, false); + else if (node.operator == SelectOperator.ge) + cursor = node.index.range(ascendingOrder, new Object[] { rightValue }, true, null, false); + else if (node.operator == SelectOperator.lt) + cursor = node.index.range(ascendingOrder, null, false, new Object[] { rightValue }, false); + else if (node.operator == SelectOperator.le) + cursor = node.index.range(ascendingOrder, null, false, new Object[] { rightValue }, true); + else + return; + } + + final SelectTreeNode parentNode = node.getParent(); + if (parentNode.operator == SelectOperator.and && parentNode.left == node) { + if (!node.index.isUnique()) { + // CHECK IF THERE IS ANOTHER INDEXED NODE ON THE SIBLING THAT IS UNIQUE (TO PREFER TO THIS) + final TypeIndex rightIndex = ((SelectTreeNode) parentNode.right).index; + if (rightIndex != null && rightIndex.isUnique()) { + // DO NOT USE THIS INDEX (NOT UNIQUE), NOT WORTH IT + node.index = null; + return; + } + } else { + // REMOVE THE INDEX ON THE SIBLING NODE + // TODO CALCULATE WHICH ONE IS FASTER AND REMOVE THE SLOWER ONE + ((SelectTreeNode) parentNode.right).index = null; + } + } + + if (usedIndexes == null) + usedIndexes = new ArrayList<>(); + + usedIndexes.add(new IndexInfo(node.index, propertyName, ascendingOrder)); + cursors.add(cursor); + } + + /** + * Considers a fully indexed node when both properties are indexed or only one with an AND operator. + */ + private boolean isTheNodeFullyIndexed(final SelectTreeNode node) { + if (node == null) + return true; + + if (!(node.left instanceof SelectTreeNode)) { + if (!(node.right instanceof SelectPropertyValue)) { + final TypeIndex propertyIndex = select.fromType.getPolymorphicIndexByProperties( + ((SelectPropertyValue) node.left).propertyName); + + if (propertyIndex != null) + node.index = propertyIndex; + + return propertyIndex != null; + } + } else { + final boolean leftIsIndexed = isTheNodeFullyIndexed((SelectTreeNode) node.left); + final boolean rightIsIndexed = isTheNodeFullyIndexed((SelectTreeNode) node.right); + + if (node.operator.equals(SelectOperator.and)) + // AND: ONE OR BOTH MEANS INDEXED + return leftIsIndexed || rightIsIndexed; + else if (node.operator.equals(SelectOperator.or)) + return leftIsIndexed || rightIsIndexed; + else if (node.operator.equals(SelectOperator.not)) + return leftIsIndexed; + } + return false; + } + + public static Object evaluateValue(final Document record, final Object value) { + if (value == null) + return null; + else if (value instanceof SelectTreeNode) + return ((SelectTreeNode) value).eval(record); + else if (value instanceof SelectRuntimeValue) + return ((SelectRuntimeValue) value).eval(record); + return value; + } + + public Map metrics() { + return Map.of("evaluatedRecords", evaluatedRecords, "usedIndexes", usedIndexes != null ? usedIndexes.size() : 0); + } + + boolean evaluateWhere(final Document record) { + ++evaluatedRecords; + final Object result = select.rootTreeElement.eval(record); + if (result instanceof Boolean) + return (Boolean) result; + throw new IllegalArgumentException("A boolean result was expected but '" + result + "' was returned"); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectIterator.java b/engine/src/main/java/com/arcadedb/query/select/SelectIterator.java new file mode 100644 index 0000000000..54637e897f --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectIterator.java @@ -0,0 +1,171 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.database.Identifiable; +import com.arcadedb.database.RID; +import com.arcadedb.serializer.BinaryComparator; +import com.arcadedb.utility.Pair; + +import java.util.*; + +/** + * Query iterator returned from queries. Extends the base Java iterator with convenient methods. + * + *

Implementation details

+ *

+ * The iterator keeps track for the returned records in case multiple indexes have being used. In fact, in case multiple + * indexes are used, it's much simpler to just return index cursor tha could overlap. In this case, the property + * `filterOutRecords` keeps track of the returning RIDs to avoid returning duplicates.

+ * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectIterator implements Iterator { + private final SelectExecutor executor; + private final Iterator iterator; + private HashSet filterOutRecords; + private T next = null; + private long returned = 0; + private List sortedResultSet; + private int orderIndex = 0; + + protected SelectIterator(final SelectExecutor executor, final Iterator iterator, + final boolean enforceUniqueReturn) { + this.executor = executor; + this.iterator = iterator; + if (enforceUniqueReturn) + this.filterOutRecords = new HashSet<>(); + + fetchResultInCaseOfOrderBy(); + + for (int i = 0; i < executor.select.skip; i++) { + // CONSUME UNTIL THE SKIP THRESHOLD IS LIMIT + if (hasNext()) + next(); + } + } + + @Override + public boolean hasNext() { + if (sortedResultSet != null) { + // RETURN FROM THE ORDERED RESULT SET + final boolean more = orderIndex < sortedResultSet.size(); + if (!more) + // EARLY DISPOSAL OF SORTED RESULT SET + sortedResultSet = null; + return more; + } + + if (executor.select.limit > -1 && returned >= executor.select.limit) + return false; + if (next != null) + return true; + if (!iterator.hasNext()) + return false; + + next = fetchNext(); + return next != null; + } + + @Override + public T next() { + if (sortedResultSet != null) + // RETURN FROM THE ORDERED RESULT SET + return (T) sortedResultSet.get(orderIndex++); + + if (next == null && !hasNext()) + throw new NoSuchElementException(); + try { + return next; + } finally { + next = null; + } + } + + private T fetchNext() { + do { + final Document record = iterator.next().asDocument(); + + if (filterOutRecords != null && filterOutRecords.contains(record.getIdentity())) + // ALREADY RETURNED, AVOID DUPLICATES IN THE RESULTSET + continue; + + if (executor.select.rootTreeElement == null || executor.evaluateWhere(record)) { + ++returned; + + if (filterOutRecords != null) + filterOutRecords.add(record.getIdentity()); + + return (T) record; + } + + } while (iterator.hasNext()); + + // NOT FOUND + return null; + } + + public T nextOrNull() { + return hasNext() ? next() : null; + } + + public List toList() { + final List result = new ArrayList<>(); + while (hasNext()) + result.add(next()); + return result; + } + + public Map getMetrics() { + return executor.metrics(); + } + + private void fetchResultInCaseOfOrderBy() { + if (executor.select.orderBy == null) + return; + + // CHECK ONLY THE CASE WITH ONE INDEX USED AND ONE ORDER BY + if (executor.select.orderBy.size() == 1 && executor.usedIndexes != null && executor.usedIndexes.size() == 1) { + final Pair orderBy = executor.select.orderBy.get(0); + final SelectExecutor.IndexInfo usedIndex = executor.usedIndexes.get(0); + + if (orderBy.getFirst().equals(usedIndex.property) &&// + orderBy.getSecond() == usedIndex.order) { + // ORDER BY THE INDEX USED, RESULTSET IS ALREADY ORDERED + return; + } + } + + final List resultSet = new ArrayList<>(); + while (hasNext()) + resultSet.add(next().asDocument(true)); + sortedResultSet = resultSet; + + Collections.sort(sortedResultSet, (a, b) -> { + for (Pair orderBy : executor.select.orderBy) { + final Object aVal = a.get(orderBy.getFirst()); + final Object bVal = b.get(orderBy.getFirst()); + int comp = BinaryComparator.compareTo(aVal, bVal); + if (comp != 0) { + if (!orderBy.getSecond()) + comp *= -1; + return comp; + } + } + return 0; + }); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectOperator.java b/engine/src/main/java/com/arcadedb/query/select/SelectOperator.java new file mode 100644 index 0000000000..7a6ce33ac3 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectOperator.java @@ -0,0 +1,146 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.query.sql.executor.QueryHelper; +import com.arcadedb.serializer.BinaryComparator; + +import java.util.*; +import java.util.concurrent.*; + +/** + * Native condition with support for simple operators through inheritance. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public enum SelectOperator { + or("or", true, 0) { + @Override + Object eval(final Document record, final Object left, final Object right) { + final Boolean leftValue = (Boolean) SelectExecutor.evaluateValue(record, left); + if (leftValue) + return true; + + return SelectExecutor.evaluateValue(record, right); + } + }, + + and("and", true, 2) { + @Override + Object eval(final Document record, final Object left, final Object right) { + final Boolean leftValue = (Boolean) SelectExecutor.evaluateValue(record, left); + if (!leftValue) + return false; + + return SelectExecutor.evaluateValue(record, right); + } + }, + + not("not", true, 2) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return left == Boolean.FALSE; + } + }, + + eq("=", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return BinaryComparator.equals(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)); + } + }, + + neq("<>", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return !BinaryComparator.equals(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)); + } + }, + + lt("<", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return BinaryComparator.compareTo(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)) < 0; + } + }, + + le("<=", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return BinaryComparator.compareTo(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)) <= 0; + } + }, + + gt(">", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return BinaryComparator.compareTo(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)) > 0; + } + }, + + ge(">=", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return BinaryComparator.compareTo(SelectExecutor.evaluateValue(record, left), + SelectExecutor.evaluateValue(record, right)) >= 0; + } + }, + + like("like", false, 1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return QueryHelper.like((String) SelectExecutor.evaluateValue(record, left), + (String) SelectExecutor.evaluateValue(record, right)); + } + }, run("!", true, -1) { + @Override + Object eval(final Document record, final Object left, final Object right) { + return SelectExecutor.evaluateValue(record, left); + } + }; + + public final String name; + public final boolean logicOperator; + public final int precedence; + private static Map NAMES = new ConcurrentHashMap<>(); + + SelectOperator(final String name, final boolean logicOperator, final int precedence) { + this.name = name; + this.logicOperator = logicOperator; + this.precedence = precedence; + } + + abstract Object eval(final Document record, Object left, Object right); + + public static SelectOperator byName(final String name) { + if (NAMES.isEmpty()) { + for (SelectOperator v : values()) + NAMES.put(v.name, v); + } + + return NAMES.get(name); + } + + @Override + public String toString() { + return name; + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectParameterValue.java b/engine/src/main/java/com/arcadedb/query/select/SelectParameterValue.java new file mode 100644 index 0000000000..cc1ab23b15 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectParameterValue.java @@ -0,0 +1,27 @@ +package com.arcadedb.query.select; + +import com.arcadedb.database.Document; + +public class SelectParameterValue implements SelectRuntimeValue { + public final String parameterName; + private final Select select; + + public SelectParameterValue(final Select select, final String parameterName) { + this.select = select; + this.parameterName = parameterName; + } + + @Override + public Object eval(final Document record) { + if (select.parameters == null) + throw new IllegalArgumentException("Missing parameter '" + parameterName + "'"); + if (!select.parameters.containsKey(parameterName)) + throw new IllegalArgumentException("Missing parameter '" + parameterName + "'"); + return select.parameters.get(parameterName); + } + + @Override + public String toString() { + return "#" + parameterName; + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectPropertyValue.java b/engine/src/main/java/com/arcadedb/query/select/SelectPropertyValue.java new file mode 100644 index 0000000000..590ec0a22c --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectPropertyValue.java @@ -0,0 +1,21 @@ +package com.arcadedb.query.select; + +import com.arcadedb.database.Document; + +public class SelectPropertyValue implements SelectRuntimeValue { + public final String propertyName; + + public SelectPropertyValue(final String propertyName) { + this.propertyName = propertyName; + } + + @Override + public Object eval(final Document record) { + return record.get(propertyName); + } + + @Override + public String toString() { + return ":" + propertyName; + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectRuntimeValue.java b/engine/src/main/java/com/arcadedb/query/select/SelectRuntimeValue.java new file mode 100644 index 0000000000..e704053210 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectRuntimeValue.java @@ -0,0 +1,11 @@ +package com.arcadedb.query.select; + +import com.arcadedb.database.Document; + +/** + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public interface SelectRuntimeValue { + + Object eval(final Document record); +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectTreeNode.java b/engine/src/main/java/com/arcadedb/query/select/SelectTreeNode.java new file mode 100644 index 0000000000..d9c4c72c5c --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectTreeNode.java @@ -0,0 +1,113 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.index.TypeIndex; +import com.arcadedb.serializer.json.JSONArray; + +/** + * Native condition representation in a tree. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectTreeNode { + public Object left; + public final SelectOperator operator; + public Object right; + private SelectTreeNode parent; + public TypeIndex index; + + public SelectTreeNode(final Object left, final SelectOperator operator, final Object right) { + this.left = left; + if (left instanceof SelectTreeNode) + ((SelectTreeNode) left).setParent(this); + + this.operator = operator; + + this.right = right; + if (right instanceof SelectTreeNode) + ((SelectTreeNode) right).setParent(this); + } + + public Object eval(final Document record) { + return operator.eval(record, left, right); + } + + public void setRight(final SelectTreeNode right) { + if (this.right != null) + throw new IllegalArgumentException("Cannot assign the right node because already assigned to " + this.right); + this.right = right; + if (right.parent != null) + throw new IllegalArgumentException("Cannot assign the parent to the right node " + right); + right.parent = this; + } + + public SelectTreeNode getParent() { + return parent; + } + + public void setParent(final SelectTreeNode newParent) { + if (this.parent == newParent) + return; + + if (this.parent != null) { + if (this.parent.left == this) { + this.parent.left = newParent; + } else if (this.parent.right == this) { + this.parent.right = newParent; + } + } + + this.parent = newParent; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append("( "); + buffer.append(left); + buffer.append(" "); + buffer.append(operator); + buffer.append(" "); + buffer.append(right); + buffer.append(" )"); + return buffer.toString(); + } + + public JSONArray toJSON() { + final JSONArray json = new JSONArray(); + + if (left instanceof SelectTreeNode) + json.put(((SelectTreeNode) left).toJSON()); + else if (left instanceof SelectPropertyValue || left instanceof SelectParameterValue) + json.put(left.toString()); + else + json.put(left); + + if (operator != SelectOperator.run) + json.put(operator.name); + + if (right != null) { + if (right instanceof SelectTreeNode) + json.put(((SelectTreeNode) right).toJSON()); + else if (right instanceof SelectPropertyValue || right instanceof SelectParameterValue) + json.put(right.toString()); + else + json.put(right); + } + return json; + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectWhereAfterFirstBlock.java b/engine/src/main/java/com/arcadedb/query/select/SelectWhereAfterFirstBlock.java new file mode 100644 index 0000000000..f4765f033d --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectWhereAfterFirstBlock.java @@ -0,0 +1,76 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +import com.arcadedb.database.Document; +import com.arcadedb.graph.Edge; +import com.arcadedb.graph.Vertex; + +import java.util.concurrent.*; + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectWhereAfterFirstBlock { + private final Select select; + + public SelectWhereAfterFirstBlock(final Select select) { + this.select = select; + } + + public SelectWhereLeftBlock and() { + return select.setLogic(SelectOperator.and); + } + + public SelectWhereLeftBlock or() { + return select.setLogic(SelectOperator.or); + } + + public SelectIterator vertices() { + return select.run(); + } + + public SelectIterator edges() { + return select.run(); + } + + public SelectIterator documents() { + return select.run(); + } + + public SelectCompiled compile() { + return select.compile(); + } + + public Select limit(final int limit) { + return select.limit(limit); + } + + public Select skip(final int skip) { + return select.skip(skip); + } + + public Select timeout(final long timeoutValue, final TimeUnit timeoutUnit, final boolean exceptionOnTimeout) { + return select.timeout(timeoutValue, timeoutUnit, exceptionOnTimeout); + } + + public Select orderBy(final String property, final boolean order) { + return select.orderBy(property, order); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectWhereBaseBlock.java b/engine/src/main/java/com/arcadedb/query/select/SelectWhereBaseBlock.java new file mode 100644 index 0000000000..cc48d1abe8 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectWhereBaseBlock.java @@ -0,0 +1,44 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public abstract class SelectWhereBaseBlock { + protected final Select select; + + public SelectWhereBaseBlock(final Select select) { + this.select = select; + } + + protected void setParameter(final String parameterName) { + select.checkNotCompiled(); + select.propertyValue = new SelectParameterValue(select, parameterName); + } + + protected void setProperty(final String name) { + select.checkNotCompiled(); + if (select.property != null) + throw new IllegalArgumentException("Property has already been set"); + if (select.state != Select.STATE.WHERE) + throw new IllegalArgumentException("No context was provided for the parameter"); + select.property = new SelectPropertyValue(name); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectWhereLeftBlock.java b/engine/src/main/java/com/arcadedb/query/select/SelectWhereLeftBlock.java new file mode 100644 index 0000000000..2f2f1dae6c --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectWhereLeftBlock.java @@ -0,0 +1,38 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectWhereLeftBlock extends SelectWhereBaseBlock { + public SelectWhereLeftBlock(final Select select) { + super(select); + } + + public SelectWhereOperatorBlock property(final String name) { + setProperty(name); + return new SelectWhereOperatorBlock(select); + } + + public SelectWhereOperatorBlock parameter(final String parameterName) { + setParameter(parameterName); + return new SelectWhereOperatorBlock(select); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectWhereOperatorBlock.java b/engine/src/main/java/com/arcadedb/query/select/SelectWhereOperatorBlock.java new file mode 100644 index 0000000000..aefa9d94a5 --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectWhereOperatorBlock.java @@ -0,0 +1,58 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectWhereOperatorBlock { + private final Select select; + + public SelectWhereOperatorBlock(final Select select) { + this.select = select; + } + + public SelectWhereRightBlock eq() { + return select.setOperator(SelectOperator.eq); + } + + public SelectWhereRightBlock neq() { + return select.setOperator(SelectOperator.neq); + } + + public SelectWhereRightBlock lt() { + return select.setOperator(SelectOperator.lt); + } + + public SelectWhereRightBlock le() { + return select.setOperator(SelectOperator.le); + } + + public SelectWhereRightBlock gt() { + return select.setOperator(SelectOperator.gt); + } + + public SelectWhereRightBlock ge() { + return select.setOperator(SelectOperator.ge); + } + + public SelectWhereRightBlock like() { + return select.setOperator(SelectOperator.like); + } +} diff --git a/engine/src/main/java/com/arcadedb/query/select/SelectWhereRightBlock.java b/engine/src/main/java/com/arcadedb/query/select/SelectWhereRightBlock.java new file mode 100644 index 0000000000..519297a7ec --- /dev/null +++ b/engine/src/main/java/com/arcadedb/query/select/SelectWhereRightBlock.java @@ -0,0 +1,57 @@ +package com.arcadedb.query.select;/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + */ + +/** + * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records + * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very + * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectWhereRightBlock extends SelectWhereBaseBlock { + + public SelectWhereRightBlock(final Select select) { + super(select); + } + + public SelectWhereAfterFirstBlock property(final String name) { + setProperty(name); + return new SelectWhereAfterFirstBlock(select); + } + + public SelectWhereAfterFirstBlock parameter(final String parameterName) { + setParameter(parameterName); + return new SelectWhereAfterFirstBlock(select); + } + + public SelectWhereAfterFirstBlock value(final Object value) { + select.checkNotCompiled(); + if (select.property == null) + throw new IllegalArgumentException("Property has not been set"); + + switch (select.state) { + case WHERE: + if (select.operator == null) + throw new IllegalArgumentException("No operator has been set"); + if (select.propertyValue != null) + throw new IllegalArgumentException("Property value has already been set"); + select.propertyValue = value; + break; + } + + return new SelectWhereAfterFirstBlock(select); + } +} diff --git a/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java b/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java index 161dcc7ebf..02e6f6d2ec 100644 --- a/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java +++ b/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java @@ -374,7 +374,11 @@ else if (b1 < b2) } public static boolean equals(final Object a, final Object b) { - if (a instanceof String && b instanceof String) + if (a == b) + return true; + else if (a == null || b == null) + return false; + else if (a instanceof String && b instanceof String) return equalsString((String) a, (String) b); else if (a instanceof byte[] && b instanceof byte[]) return equalsBytes((byte[]) a, (byte[]) b); diff --git a/engine/src/main/java/com/arcadedb/utility/MultiIterator.java b/engine/src/main/java/com/arcadedb/utility/MultiIterator.java index 29f1b3e5b9..4d806538d9 100755 --- a/engine/src/main/java/com/arcadedb/utility/MultiIterator.java +++ b/engine/src/main/java/com/arcadedb/utility/MultiIterator.java @@ -31,13 +31,14 @@ public class MultiIterator implements ResettableIterator, Iterable { private Iterator sourcesIterator; private Iterator partialIterator; - private long browsed = 0L; - private long skip = -1L; - private long limit = -1L; - private long timeout = -1L; - private boolean embedded = false; - private int skipped = 0; - private final long beginTime = System.currentTimeMillis(); + private long browsed = 0L; + private long skip = -1L; + private long limit = -1L; + private long timeout = -1L; + private boolean exceptionOnTimeout = false; + private boolean embedded = false; + private int skipped = 0; + private final long beginTime = System.currentTimeMillis(); public MultiIterator() { sources = new ArrayList<>(); @@ -61,8 +62,9 @@ public boolean hasNext() { } private boolean hasNextInternal() { - if (timeout > -1L && System.currentTimeMillis() - beginTime > timeout) + if (timeout > -1L && System.currentTimeMillis() - beginTime > timeout) { throw new TimeoutException("Timeout on iteration"); + } if (sourcesIterator == null) { if (sources == null || sources.isEmpty()) @@ -125,8 +127,8 @@ public long countEntries() { long size = 0; final int totSources = sources.size(); for (int i = 0; i < totSources; ++i) { - if (timeout > -1L && System.currentTimeMillis() - beginTime > timeout) - throw new TimeoutException("Timeout on iteration"); + if (checkForTimeout()) + break; final Object o = sources.get(i); @@ -163,8 +165,9 @@ public void setLimit(final long limit) { this.limit = limit; } - public void setTimeout(final long readTimeout) { + public void setTimeout(final long readTimeout, final boolean exceptionOnTimeout) { this.timeout = readTimeout; + this.exceptionOnTimeout = exceptionOnTimeout; } public long getSkip() { @@ -193,8 +196,8 @@ public boolean contains(final Object value) { @SuppressWarnings("unchecked") protected boolean getNextPartial() { - if (timeout > -1L && System.currentTimeMillis() - beginTime > timeout) - throw new TimeoutException("Timeout on iteration"); + if (checkForTimeout()) + return false; if (sourcesIterator != null) while (sourcesIterator.hasNext()) { @@ -233,6 +236,15 @@ protected boolean getNextPartial() { return false; } + private boolean checkForTimeout() { + if (timeout > -1L && System.currentTimeMillis() - beginTime > timeout) + if (exceptionOnTimeout) + throw new TimeoutException("Timeout on iteration"); + else + return true; + return false; + } + public boolean isEmbedded() { return embedded; } diff --git a/engine/src/main/java/com/arcadedb/utility/VariableParser.java b/engine/src/main/java/com/arcadedb/utility/VariableParser.java index 424b8486ef..886fae74d5 100644 --- a/engine/src/main/java/com/arcadedb/utility/VariableParser.java +++ b/engine/src/main/java/com/arcadedb/utility/VariableParser.java @@ -19,6 +19,7 @@ package com.arcadedb.utility; import com.arcadedb.log.LogManager; +import org.apache.lucene.codecs.CodecUtil; import java.util.logging.*; @@ -28,39 +29,44 @@ * @author Luca Garulli (luca.garulli--at--assetdata.it) */ public class VariableParser { - public static Object resolveVariables(final String iText, final String iBegin, final String iEnd, final VariableParserListener iListener) { + public static Object resolveVariables(final String iText, final String iBegin, final String iEnd, + final VariableParserListener iListener) { return resolveVariables(iText, iBegin, iEnd, iListener, null); } - public static Object resolveVariables(final String iText, final String iBegin, final String iEnd, final VariableParserListener iListener, - final Object iDefaultValue) { - if (iListener == null) + public static Object resolveVariables(final String text, final String beginPattern, final String endPattern, + final VariableParserListener listener, final Object defaultValue) { + if (listener == null) throw new IllegalArgumentException("Missed VariableParserListener listener"); - final int beginPos = iText.lastIndexOf(iBegin); + final int beginPos = text.lastIndexOf(beginPattern); if (beginPos == -1) - return iText; + return text; - final int endPos = iText.indexOf(iEnd, beginPos + 1); + final int endPos = text.indexOf(endPattern, beginPos + 1); if (endPos == -1) - return iText; + return text; - final String pre = iText.substring(0, beginPos); - final String var = iText.substring(beginPos + iBegin.length(), endPos); - final String post = iText.substring(endPos + iEnd.length()); + final String pre = text.substring(0, beginPos); + String var = text.substring(beginPos + beginPattern.length(), endPos); + final String post = text.substring(endPos + endPattern.length()); - Object resolved = iListener.resolve(var); + // DECODE INTERNAL + var = var.replace("$\\{", "${"); + var = var.replace("\\}", "}"); + + Object resolved = listener.resolve(var); if (resolved == null) { - if (iDefaultValue == null) - LogManager.instance().log(null, Level.INFO, "[VariableParser.resolveVariables] Error on resolving property: %s", var); + if (defaultValue == null) + LogManager.instance().log(null, Level.INFO, "Error on resolving property: %s", var); else - resolved = iDefaultValue; + resolved = defaultValue; } if (pre.length() > 0 || post.length() > 0) { final String path = pre + (resolved != null ? resolved.toString() : "") + post; - return resolveVariables(path, iBegin, iEnd, iListener); + return resolveVariables(path, beginPattern, endPattern, listener); } return resolved; diff --git a/engine/src/test/java/com/arcadedb/TestHelper.java b/engine/src/test/java/com/arcadedb/TestHelper.java index 0b24bbda35..c02cd939fe 100644 --- a/engine/src/test/java/com/arcadedb/TestHelper.java +++ b/engine/src/test/java/com/arcadedb/TestHelper.java @@ -62,8 +62,6 @@ protected TestHelper(final boolean cleanBeforeTest) { database = factory.exists() ? factory.open() : factory.create(); Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); - database.async().setParallelLevel(PARALLEL_LEVEL); - if (autoStartTx) database.begin(); } diff --git a/engine/src/test/java/com/arcadedb/query/select/SelectExecutionIT.java b/engine/src/test/java/com/arcadedb/query/select/SelectExecutionIT.java new file mode 100644 index 0000000000..1008dc61ab --- /dev/null +++ b/engine/src/test/java/com/arcadedb/query/select/SelectExecutionIT.java @@ -0,0 +1,369 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + * + * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com) + * SPDX-License-Identifier: Apache-2.0 + */ +package com.arcadedb.query.select; + +import com.arcadedb.TestHelper; +import com.arcadedb.engine.Bucket; +import com.arcadedb.engine.Component; +import com.arcadedb.exception.TimeoutException; +import com.arcadedb.graph.Vertex; +import com.arcadedb.schema.Schema; +import com.arcadedb.schema.Type; +import com.arcadedb.serializer.json.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.*; + +/** + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectExecutionIT extends TestHelper { + + public SelectExecutionIT() { + autoStartTx = true; + } + + @Override + protected void beginTest() { + database.getSchema().createDocumentType("Document"); + database.getSchema().createVertexType("Vertex")// + .createProperty("id", Type.INTEGER)// + .createIndex(Schema.INDEX_TYPE.LSM_TREE, true); + database.getSchema().createEdgeType("Edge"); + + database.transaction(() -> { + for (int i = 0; i < 100; i++) { + database.newDocument("Document").set("id", i, "float", 3.14F, "name", "Elon").save(); + database.newVertex("Vertex").set("id", i, "float", 3.14F, "name", "Elon").save(); + } + + for (int i = 1; i < 100; i++) { + final Vertex root = database.select().fromType("Vertex").where().property("id").eq().value(0).vertices().nextOrNull(); + Assertions.assertNotNull(root); + Assertions.assertEquals(0, root.getInteger("id")); + + root.newEdge("Edge", database.select().fromType("Vertex").where().property("id").eq().value(i).vertices().nextOrNull(), + true).save(); + } + }); + } + + @Test + public void okFromBuckets() { + { + final SelectCompiled select = database.select().fromBuckets( + database.getSchema().getType("Vertex").getBuckets(true).stream().map(Component::getName).collect(Collectors.toList()) + .toArray(new String[database.getSchema().getType("Vertex").getBuckets(true).size()]))// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 100; i++) + Assertions.assertEquals(i, select.parameter("value", i).vertices().nextOrNull().getInteger("id")); + } + + { + final SelectCompiled select = database.select().fromBuckets( + database.getSchema().getType("Vertex").getBuckets(true).stream().map(Bucket::getFileId).collect(Collectors.toList()) + .toArray(new Integer[database.getSchema().getType("Vertex").getBuckets(true).size()]))// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 100; i++) + Assertions.assertEquals(i, select.parameter("value", i).vertices().nextOrNull().getInteger("id")); + } + } + + @Test + public void okAnd() { + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 100; i++) + Assertions.assertEquals(i, select.parameter("value", i).vertices().nextOrNull().getInteger("id")); + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon2").compile(); + + Assertions.assertFalse(select.parameter("value", 3).vertices().hasNext()); + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().value(-1)// + .and().property("name").eq().value("Elon").compile(); + + Assertions.assertFalse(select.vertices().hasNext()); + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon")// + .and().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 100; i++) + Assertions.assertEquals(i, select.parameter("value", i).vertices().nextOrNull().getInteger("id")); + } + } + + @Test + public void okOr() { + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("name").eq().value("Elon").compile(); + + for (SelectIterator result = select.parameter("value", 3).vertices(); result.hasNext(); ) { + final Vertex v = result.next(); + Assertions.assertTrue(v.getInteger("id").equals(3) || v.getString("name").equals("Elon")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("name").eq().value("Elon2").compile(); + + for (SelectIterator result = select.parameter("value", 3).vertices(); result.hasNext(); ) { + final Vertex v = result.next(); + Assertions.assertTrue(v.getInteger("id").equals(3) || v.getString("name").equals("Elon2")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().value(-1)// + .or().property("name").eq().value("Elon").compile(); + + for (SelectIterator result = select.parameter("value", 3).vertices(); result.hasNext(); ) { + final Vertex v = result.next(); + Assertions.assertTrue(v.getInteger("id").equals(-1) || v.getString("name").equals("Elon")); + } + } + } + + @Test + public void okAndOr() { + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon2")// + .or().property("name").eq().value("Elon").compile(); + + for (SelectIterator result = select.parameter("value", 3).vertices(); result.hasNext(); ) { + final Vertex v = result.next(); + Assertions.assertTrue(v.getInteger("id").equals(3) && v.getString("name").equals("Elon2") ||// + v.getString("name").equals("Elon")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("name").eq().value("Elon2")// + .and().property("name").eq().value("Elon").compile(); + + for (SelectIterator result = select.parameter("value", 3).vertices(); result.hasNext(); ) { + final Vertex v = result.next(); + Assertions.assertTrue(v.getInteger("id").equals(3) ||// + v.getString("name").equals("Elon2") && v.getString("name").equals("Elon")); + } + } + } + + @Test + public void okLimit() { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").limit(10).compile(); + + final SelectIterator iter = select.vertices(); + int browsed = 0; + while (iter.hasNext()) { + Assertions.assertTrue(iter.next().getInteger("id") < 10); + ++browsed; + } + Assertions.assertEquals(10, browsed); + } + + @Test + public void okSkip() { + SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").skip(10).compile(); + + SelectIterator iter = select.vertices(); + int browsed = 0; + while (iter.hasNext()) { + Assertions.assertTrue(iter.next().getInteger("id") < 10); + ++browsed; + } + Assertions.assertEquals(0, browsed); + + select = database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").skip(0).compile(); + + iter = select.vertices(); + browsed = 0; + while (iter.hasNext()) { + Assertions.assertTrue(iter.next().getInteger("id") < 10); + ++browsed; + } + Assertions.assertEquals(10, browsed); + + select = database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").skip(2).compile(); + + iter = select.vertices(); + browsed = 0; + while (iter.hasNext()) { + Assertions.assertTrue(iter.next().getInteger("id") < 10); + ++browsed; + } + Assertions.assertEquals(8, browsed); + } + + @Test + public void okUpdate() { + database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon")// + .limit(10).vertices()// + .forEachRemaining(a -> a.modify().set("modified", true).save()); + + database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").limit(10).vertices() + .forEachRemaining(r -> Assertions.assertTrue(r.getInteger("id") < 10 && r.getBoolean("modified"))); + } + + @Test + public void errorTimeout() { + { + expectingException(() -> { + final SelectIterator iter = database.select().fromType("Vertex")// + .where().property("name").eq().value("Elon").timeout(1, TimeUnit.MILLISECONDS, true).vertices(); + + while (iter.hasNext()) { + Assertions.assertTrue(iter.next().getInteger("id") < 10); + try { + Thread.sleep(2); + } catch (InterruptedException e) { + // IGNORE IT + } + } + }, TimeoutException.class, "Timeout on iteration"); + } + + { + final SelectIterator iter = database.select().fromType("Vertex")// + .where().property("id").lt().value(10)// + .and().property("name").eq().value("Elon").timeout(1, TimeUnit.MILLISECONDS, false).vertices(); + } + } + + @Test + public void okNeq() { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").neq().parameter("value").compile(); + + for (int i = 0; i < 100; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(99, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") != finalI)); + } + } + + @Test + public void okLike() { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("name").like().value("E%").compile(); + + for (int i = 0; i < 100; i++) { + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(100, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getString("name").startsWith("E"))); + } + } + + @Test + public void errorMissingParameter() { + expectingException(() -> { + database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .vertices().nextOrNull(); + }, IllegalArgumentException.class, "Missing parameter 'value'"); + } + + @Test + public void okReuse() { + final SelectCompiled select = database.select().fromType("Vertex").where().property("id").eq().parameter("value").compile(); + for (int i = 0; i < 100; i++) + Assertions.assertEquals(i, select.parameter("value", i).vertices().nextOrNull().getInteger("id")); + } + + @Test + public void okJSON() { + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon2")// + .or().property("name").eq().value("Elon").compile(); + + final JSONObject json = select.json(); + + final JSONObject json2 = database.select().json(json).compile().json(); + + Assertions.assertEquals(json, json2); + } + } + + private void expectingException(final Runnable callback, final Class expectedException, + final String mustContains) { + boolean failed = true; + try { + callback.run(); + failed = false; + } catch (Throwable e) { + if (!expectedException.equals(e.getClass())) + e.printStackTrace(); + + Assertions.assertEquals(expectedException, e.getClass()); + Assertions.assertTrue(e.getMessage().contains(mustContains), + "Expected '" + mustContains + "' in the error message. Error message is: " + e.getMessage()); + } + + if (!failed) + Assertions.fail("Expected exception " + expectedException); + } +} diff --git a/engine/src/test/java/com/arcadedb/query/select/SelectIndexExecutionIT.java b/engine/src/test/java/com/arcadedb/query/select/SelectIndexExecutionIT.java new file mode 100644 index 0000000000..c3f562861c --- /dev/null +++ b/engine/src/test/java/com/arcadedb/query/select/SelectIndexExecutionIT.java @@ -0,0 +1,270 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + * + * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com) + * SPDX-License-Identifier: Apache-2.0 + */ +package com.arcadedb.query.select; + +import com.arcadedb.TestHelper; +import com.arcadedb.graph.Vertex; +import com.arcadedb.schema.Schema; +import com.arcadedb.schema.Type; +import com.arcadedb.schema.VertexType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.*; + +/** + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectIndexExecutionIT extends TestHelper { + + public SelectIndexExecutionIT() { + autoStartTx = true; + } + + @Override + protected void beginTest() { + final VertexType v = database.getSchema().createVertexType("Vertex"); + v.createProperty("id", Type.INTEGER)// + .createIndex(Schema.INDEX_TYPE.LSM_TREE, true); + v.createProperty("name", Type.STRING)// + .createIndex(Schema.INDEX_TYPE.LSM_TREE, false); + + database.transaction(() -> { + for (int i = 0; i < 100; i++) + database.newVertex("Vertex").set("id", i, "float", 3.14F, "name", "Elon").save(); + for (int i = 100; i < 110; i++) + database.newVertex("Vertex").set("id", i, "name", "Jay").save(); + }); + } + + @Test + public void okOneOfTwoAvailableIndexes() { + // EXPECTED TO USE BOTH INDEXES BECAUSE OF THE AND LOGIC OPERATOR + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + + final List list = result.toList(); + Assertions.assertEquals(i < 100 ? 1 : 0, list.size()); + + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") == finalI && r.getString("name").equals("Elon"))); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(1L, result.getMetrics().get("evaluatedRecords"), "With id " + i); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + } + + @Test + public void okBothAvailableIndexes() { + // EXPECTED TO USE BOTH INDEXES BECAUSE OF THE OR LOGIC OPERATOR AND EACH PROPERTY IS INDEXED + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("name").eq().value("Elon").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + + result.forEachRemaining(r -> Assertions.assertTrue(r.getInteger("id") == finalI || r.getString("name").equals("Elon"))); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(i < 100 ? 100L : 101L, result.getMetrics().get("evaluatedRecords"), "" + finalI); + Assertions.assertEquals(2, result.getMetrics().get("usedIndexes")); + } + } + } + + @Test + public void okOneIndexUsed() { + // EXPECTED TO USE ONLY ONE INDEX + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .and().property("unknown").eq().value(null).compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + + result.forEachRemaining(r -> Assertions.assertEquals((int) r.getInteger("id"), finalI)); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(1L, result.getMetrics().get("evaluatedRecords")); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + + // EXPECTED TO USE ONLY ONE INDEX + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("unknown").eq().value(null)// + .and().property("id").eq().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + + result.forEachRemaining(r -> Assertions.assertEquals((int) r.getInteger("id"), finalI)); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(1L, result.getMetrics().get("evaluatedRecords")); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + } + + @Test + public void okNoIndexUsed() { + // EXPECTED NO INDEXES IS USED BECAUSE NO INDEXES WERE DEFINED ON ANY OF THE PROPERTIES + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("unknown").eq().value(null)// + .and().property("unknown").eq().value(null).compile(); + + for (int i = 0; i < 110; i++) { + final SelectIterator result = select.parameter("value", i).vertices(); + result.toList(); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(110L, result.getMetrics().get("evaluatedRecords")); + Assertions.assertEquals(0, result.getMetrics().get("usedIndexes")); + } + } + + // EXPECTED NO INDEXES IS USED BECAUSE THE OR OPERATOR ONLY ONE ONE PROPERTY + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("unknown").eq().value(null).compile(); + + for (int i = 0; i < 110; i++) { + final SelectIterator result = select.parameter("value", i).vertices(); + result.toList(); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(110L, result.getMetrics().get("evaluatedRecords")); + Assertions.assertEquals(0, result.getMetrics().get("usedIndexes")); + } + } + + // EXPECTED NO INDEXES IS USED BECAUSE THE OR OPERATOR ONLY ONE ONE PROPERTY + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").eq().parameter("value")// + .or().property("unknown").eq().value(null).and().property("id").eq().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + + final SelectIterator result = select.parameter("value", i).vertices(); + + final List list = result.toList(); + Assertions.assertEquals(1, list.size()); + + list.forEach(r -> Assertions.assertEquals((int) r.getInteger("id"), finalI)); + + // CHECK 1 FOR ID = I + 100 FOR NAME = ELON (ALL OF THEM) + Assertions.assertEquals(1L, result.getMetrics().get("evaluatedRecords")); + Assertions.assertEquals(2, result.getMetrics().get("usedIndexes")); + } + } + + } + + @Test + public void okRanges() { + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").gt().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(109 - i, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") > finalI)); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").ge().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(110 - i, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") >= finalI)); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").lt().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(i, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") < finalI)); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").le().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(i + 1, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") <= finalI)); + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + } + + { + final SelectCompiled select = database.select().fromType("Vertex")// + .where().property("id").neq().parameter("value").compile(); + + for (int i = 0; i < 110; i++) { + final int finalI = i; + final SelectIterator result = select.parameter("value", i).vertices(); + final List list = result.toList(); + Assertions.assertEquals(109, list.size()); + list.forEach(r -> Assertions.assertTrue(r.getInteger("id") != finalI)); + Assertions.assertEquals(0, result.getMetrics().get("usedIndexes")); + } + } + } +} diff --git a/engine/src/test/java/com/arcadedb/query/select/SelectOrderByIT.java b/engine/src/test/java/com/arcadedb/query/select/SelectOrderByIT.java new file mode 100644 index 0000000000..c87f2c4018 --- /dev/null +++ b/engine/src/test/java/com/arcadedb/query/select/SelectOrderByIT.java @@ -0,0 +1,121 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * 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. + * + * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com) + * SPDX-License-Identifier: Apache-2.0 + */ +package com.arcadedb.query.select; + +import com.arcadedb.TestHelper; +import com.arcadedb.graph.Vertex; +import com.arcadedb.schema.Schema; +import com.arcadedb.schema.Type; +import com.arcadedb.schema.VertexType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class SelectOrderByIT extends TestHelper { + + public SelectOrderByIT() { + autoStartTx = true; + } + + @Override + protected void beginTest() { + final VertexType v = database.getSchema().createVertexType("Vertex"); + v.createProperty("id", Type.INTEGER)// + .createIndex(Schema.INDEX_TYPE.LSM_TREE, true); + v.createProperty("name", Type.STRING)// + .createIndex(Schema.INDEX_TYPE.LSM_TREE, false); + + database.transaction(() -> { + for (int i = 0; i < 100; i++) + database.newVertex("Vertex").set("id", i, "notIndexedId", i, "float", i + 3.14F, "name", "Elon").save(); + for (int i = 100; i < 110; i++) + database.newVertex("Vertex").set("id", i, "notIndexedId", i, "name", "Jay").save(); + }); + } + + @Test + public void okOrderByNoIndex() { + // ASCENDING + { + final SelectCompiled select = database.select().fromType("Vertex").orderBy("notIndexedId", true).compile(); + int lastId = -1; + final SelectIterator result = select.vertices(); + + while (result.hasNext()) { + final Integer id = result.next().getInteger("notIndexedId"); + Assertions.assertTrue(id > lastId); + lastId = id; + } + + Assertions.assertEquals(0, result.getMetrics().get("usedIndexes")); + } + + // DESCENDING + { + final SelectCompiled select = database.select().fromType("Vertex").orderBy("notIndexedId", false).compile(); + int lastId = Integer.MAX_VALUE; + final SelectIterator result = select.vertices(); + + while (result.hasNext()) { + final Integer id = result.next().getInteger("notIndexedId"); + Assertions.assertTrue(id < lastId); + lastId = id; + } + + Assertions.assertEquals(0, result.getMetrics().get("usedIndexes")); + + } + } + + @Test + public void okOrderBy1Index() { + // ASCENDING + { + final SelectCompiled select = database.select().fromType("Vertex").where().property("id").gt().value(-1).orderBy("id", true).compile(); + int lastId = -1; + final SelectIterator result = select.vertices(); + + while (result.hasNext()) { + final Integer id = result.next().getInteger("id"); + Assertions.assertTrue(id > lastId); + lastId = id; + } + + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + } + + // DESCENDING + { + final SelectCompiled select = database.select().fromType("Vertex").where().property("id").gt().value(-1).orderBy("id", false).compile(); + int lastId = Integer.MAX_VALUE; + final SelectIterator result = select.vertices(); + + while (result.hasNext()) { + final Integer id = result.next().getInteger("id"); + Assertions.assertTrue(id < lastId); + lastId = id; + } + + Assertions.assertEquals(1, result.getMetrics().get("usedIndexes")); + + } + } +} diff --git a/engine/src/test/java/com/arcadedb/query/sql/method/collection/SQLMethodTransformTest.java b/engine/src/test/java/com/arcadedb/query/sql/method/collection/SQLMethodTransformTest.java index d0fba1feae..a7891087d1 100644 --- a/engine/src/test/java/com/arcadedb/query/sql/method/collection/SQLMethodTransformTest.java +++ b/engine/src/test/java/com/arcadedb/query/sql/method/collection/SQLMethodTransformTest.java @@ -49,6 +49,7 @@ import com.arcadedb.graph.Vertex; import com.arcadedb.index.IndexCursor; import com.arcadedb.query.QueryEngine; +import com.arcadedb.query.select.Select; import com.arcadedb.query.sql.SQLQueryEngine; import com.arcadedb.query.sql.executor.BasicCommandContext; import com.arcadedb.query.sql.executor.ResultSet; @@ -351,6 +352,11 @@ public String getCurrentUserName() { return null; } + @Override + public Select select() { + return null; + } + @Override public ResultSet command(String language, String query, Map args) { return null; diff --git a/engine/src/test/java/performance/LocalDatabaseBenchmark.java b/engine/src/test/java/performance/LocalDatabaseBenchmark.java index 8bd26538eb..fad727fd12 100644 --- a/engine/src/test/java/performance/LocalDatabaseBenchmark.java +++ b/engine/src/test/java/performance/LocalDatabaseBenchmark.java @@ -23,17 +23,18 @@ import com.arcadedb.GlobalConfiguration; import com.arcadedb.database.Database; import com.arcadedb.database.DatabaseFactory; -import com.arcadedb.database.DatabaseInternal; import com.arcadedb.exception.ConcurrentModificationException; import com.arcadedb.graph.MutableVertex; +import com.arcadedb.query.select.SelectCompiled; import org.junit.jupiter.api.Assertions; import java.util.*; import java.util.concurrent.atomic.*; public class LocalDatabaseBenchmark { - private static final int TOTAL = 10_000_000; - private static final int BATCH_TX = 200; + private static final int TOTAL = 1_000; + private static final int BATCH_TX = 200; + private static final int PRINT_EVERY_MS = 1_000; private static final int BUCKETS = 7; private static final int CONCURRENT_THREADS = BUCKETS; @@ -120,9 +121,43 @@ public void run() { Assertions.assertEquals(TOTAL * CONCURRENT_THREADS, database.countType("User", true)); +// queryNative(); +// querySQL(); +// queryNative(); + database.close(); } + private void queryNative() { + final long begin = System.currentTimeMillis(); + final SelectCompiled cached = database.select().fromType("User").where()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id").and()// + .property("id").eq().parameter("id")// + .compile(); + for (int i = 0; i < TOTAL * CONCURRENT_THREADS; i++) { + Assertions.assertEquals(1, cached.parameter("id", i).vertices().toList().size()); + } + System.out.println("NATIVE " + (System.currentTimeMillis() - begin) + "ms"); + } + + private void querySQL() { + long begin = System.currentTimeMillis(); + for (int i = 0; i < TOTAL * CONCURRENT_THREADS; i++) { + Assertions.assertEquals(1, database.query("sql", + "select from User where id = ? and id = ? and id = ? and id = ? and id = ? and id = ? and id = ? and id = ? and id = ? and id = ?", + i, i, i, i, i, i, i, i, i, i).toVertices().size()); + } + System.out.println("SQL " + (System.currentTimeMillis() - begin) + "ms"); + } + private List checkRecordSequence(final Database database) { final List allIds = new ArrayList<>(); database.iterateType("User", true).forEachRemaining((a) -> allIds.add(a.getRecord().asVertex().getLong("id"))); @@ -185,7 +220,7 @@ private void executeInThread(final int threadId) { } catch (Throwable t) { incrementError(t); } finally { - mergeStats(((DatabaseInternal) database).getStats()); + mergeStats(database.getStats()); } } @@ -201,12 +236,12 @@ private long printStats(long beginTime) { final long delta = now - beginTime; beginTime = System.currentTimeMillis(); System.out.println( - ((globalCounter.get() - lastCounter.get()) * PRINT_EVERY_MS / (float) delta) + " req/sec (counter=" + globalCounter.get() + "/" + (CONCURRENT_THREADS - * TOTAL) + ", conflicts=" + concurrentExceptions.get() + ", errors=" + errors.get() + ")"); + ((globalCounter.get() - lastCounter.get()) * PRINT_EVERY_MS / (float) delta) + " req/sec (counter=" + globalCounter.get() + + "/" + (CONCURRENT_THREADS * TOTAL) + ", conflicts=" + concurrentExceptions.get() + ", errors=" + errors.get() + + ")"); } else { - System.out.println( - "COMPLETED (counter=" + globalCounter.get() + "/" + (CONCURRENT_THREADS * TOTAL) + ", conflicts=" + concurrentExceptions.get() + ", errors=" - + errors.get() + ")"); + System.out.println("COMPLETED (counter=" + globalCounter.get() + "/" + (CONCURRENT_THREADS * TOTAL) + ", conflicts=" + + concurrentExceptions.get() + ", errors=" + errors.get() + ")"); } lastCounter.set(globalCounter.get()); diff --git a/server/src/main/java/com/arcadedb/server/ServerDatabase.java b/server/src/main/java/com/arcadedb/server/ServerDatabase.java index fae43b35d1..a0ad9a7e4f 100644 --- a/server/src/main/java/com/arcadedb/server/ServerDatabase.java +++ b/server/src/main/java/com/arcadedb/server/ServerDatabase.java @@ -49,6 +49,7 @@ import com.arcadedb.graph.Vertex; import com.arcadedb.index.IndexCursor; import com.arcadedb.query.QueryEngine; +import com.arcadedb.query.select.Select; import com.arcadedb.query.sql.executor.ResultSet; import com.arcadedb.query.sql.parser.ExecutionPlanCache; import com.arcadedb.query.sql.parser.StatementCache; @@ -109,6 +110,11 @@ public String getCurrentUserName() { return wrapped.getCurrentUserName(); } + @Override + public Select select() { + return wrapped.select(); + } + @Override public Map alignToReplicas() { throw new UnsupportedOperationException("Align Database not supported"); @@ -164,7 +170,8 @@ public void scanType(final String typeName, final boolean polymorphic, final Doc } @Override - public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, final ErrorRecordCallback errorRecordCallback) { + public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, + final ErrorRecordCallback errorRecordCallback) { wrapped.scanType(typeName, polymorphic, callback, errorRecordCallback); } @@ -337,7 +344,8 @@ public boolean transaction(final TransactionScope txBlock, final boolean joinCur } @Override - public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int attempts, final OkCallback ok, final ErrorCallback error) { + public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int attempts, final OkCallback ok, + final ErrorCallback error) { return wrapped.transaction(txBlock, joinCurrentTx, attempts, ok, error); } @@ -378,19 +386,20 @@ public MutableVertex newVertex(final String typeName) { } @Override - public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, final Object[] sourceVertexKeyValues, - final String destinationVertexType, final String[] destinationVertexKeyNames, final Object[] destinationVertexKeyValues, - final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, final Object... properties) { - return wrapped.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType, destinationVertexKeyNames, - destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties); + public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, + final Object[] sourceVertexKeyValues, final String destinationVertexType, final String[] destinationVertexKeyNames, + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { + return wrapped.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType, + destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties); } @Override public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVertexType, final String[] destinationVertexKeyNames, - final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, - final Object... properties) { - return wrapped.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, - bidirectional, properties); + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { + return wrapped.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues, + createVertexIfNotExist, edgeType, bidirectional, properties); } @Override @@ -437,7 +446,8 @@ public DocumentIndexer getIndexer() { } @Override - public ResultSet command(final String language, final String query, final ContextConfiguration configuration, final Object... args) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Object... args) { return wrapped.command(language, query, configuration, args); } @@ -452,7 +462,8 @@ public ResultSet command(final String language, final String query, final Map args) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Map args) { return wrapped.command(language, query, configuration, args); } diff --git a/server/src/main/java/com/arcadedb/server/ha/ReplicatedDatabase.java b/server/src/main/java/com/arcadedb/server/ha/ReplicatedDatabase.java index 7f053c4e45..210c98ff03 100644 --- a/server/src/main/java/com/arcadedb/server/ha/ReplicatedDatabase.java +++ b/server/src/main/java/com/arcadedb/server/ha/ReplicatedDatabase.java @@ -56,6 +56,7 @@ import com.arcadedb.index.IndexCursor; import com.arcadedb.network.binary.ServerIsNotTheLeaderException; import com.arcadedb.query.QueryEngine; +import com.arcadedb.query.select.Select; import com.arcadedb.query.sql.executor.ResultSet; import com.arcadedb.query.sql.parser.ExecutionPlanCache; import com.arcadedb.query.sql.parser.StatementCache; @@ -117,8 +118,8 @@ public void commit() { replicateTx(tx, phase1, bufferChanges); else { // USE A BIGGER TIMEOUT CONSIDERING THE DOUBLE LATENCY - final TxForwardRequest command = new TxForwardRequest(ReplicatedDatabase.this, getTransactionIsolationLevel(), tx.getBucketRecordDelta(), - bufferChanges, tx.getIndexChanges().toMap()); + final TxForwardRequest command = new TxForwardRequest(ReplicatedDatabase.this, getTransactionIsolationLevel(), + tx.getBucketRecordDelta(), bufferChanges, tx.getIndexChanges().toMap()); server.getHA().forwardCommandToLeader(command, timeout * 2); tx.reset(); } @@ -144,7 +145,8 @@ public void commit() { }); } - public void replicateTx(final TransactionContext tx, final TransactionContext.TransactionPhase1 phase1, final Binary bufferChanges) { + public void replicateTx(final TransactionContext tx, final TransactionContext.TransactionPhase1 phase1, + final Binary bufferChanges) { final int configuredServers = server.getHA().getConfiguredServers(); final int reqQuorum; @@ -357,6 +359,11 @@ public String getCurrentUserName() { return proxied.getCurrentUserName(); } + @Override + public Select select() { + return proxied.select(); + } + @Override public ContextConfiguration getConfiguration() { return proxied.getConfiguration(); @@ -428,7 +435,8 @@ public void scanType(final String typeName, final boolean polymorphic, final Doc } @Override - public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, final ErrorRecordCallback errorRecordCallback) { + public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback, + final ErrorRecordCallback errorRecordCallback) { proxied.scanType(typeName, polymorphic, callback, errorRecordCallback); } @@ -504,11 +512,11 @@ public MutableVertex newVertex(final String typeName) { @Override public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVertexType, final String[] destinationVertexKeyNames, - final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, - final Object... properties) { + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { - return proxied.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, - bidirectional, properties); + return proxied.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues, + createVertexIfNotExist, edgeType, bidirectional, properties); } @Override @@ -517,12 +525,13 @@ public QueryEngine getQueryEngine(final String language) { } @Override - public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, final Object[] sourceVertexKeyValues, - final String destinationVertexType, final String[] destinationVertexKeyNames, final Object[] destinationVertexKeyValues, - final boolean createVertexIfNotExist, final String edgeType, final boolean bidirectional, final Object... properties) { + public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames, + final Object[] sourceVertexKeyValues, final String destinationVertexType, final String[] destinationVertexKeyNames, + final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType, + final boolean bidirectional, final Object... properties) { - return proxied.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType, destinationVertexKeyNames, - destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties); + return proxied.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType, + destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties); } @Override @@ -551,7 +560,8 @@ public boolean transaction(final TransactionScope txBlock, final boolean joinCur } @Override - public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int retries, final OkCallback ok, final ErrorCallback error) { + public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int retries, final OkCallback ok, + final ErrorCallback error) { return proxied.transaction(txBlock, joinCurrentTx, retries, ok, error); } @@ -586,7 +596,8 @@ public boolean equals(final Object o) { } @Override - public ResultSet command(final String language, final String query, final ContextConfiguration configuration, final Object... args) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Object... args) { if (!isLeader()) { final QueryEngine queryEngine = proxied.getQueryEngineManager().getInstance(language, this); if (queryEngine.isExecutedByTheLeader() || queryEngine.analyze(query).isDDL()) { @@ -611,7 +622,8 @@ public ResultSet command(final String language, final String query, final Map args) { + public ResultSet command(final String language, final String query, final ContextConfiguration configuration, + final Map args) { if (!isLeader()) { final QueryEngine queryEngine = proxied.getQueryEngineManager().getInstance(language, this); if (queryEngine.isExecutedByTheLeader() || queryEngine.analyze(query).isDDL()) { @@ -811,7 +823,8 @@ public Map alignToReplicas() { fileSizes.put(file.getFileId(), file.getSize()); } - final DatabaseAlignRequest request = new DatabaseAlignRequest(getName(), getSchema().getEmbedded().toJSON().toString(), fileChecksums, fileSizes); + final DatabaseAlignRequest request = new DatabaseAlignRequest(getName(), getSchema().getEmbedded().toJSON().toString(), + fileChecksums, fileSizes); final List responsePayloads = ha.sendCommandToReplicasWithQuorum(request, quorum, 120_000); if (responsePayloads != null) {