diff --git a/evita_engine/src/main/java/io/evitadb/core/query/QueryContext.java b/evita_engine/src/main/java/io/evitadb/core/query/QueryContext.java index 81b754874b..3df17e658c 100644 --- a/evita_engine/src/main/java/io/evitadb/core/query/QueryContext.java +++ b/evita_engine/src/main/java/io/evitadb/core/query/QueryContext.java @@ -503,6 +503,18 @@ public int translateEntity(@Nonnull EntityContract entity) { } } + /** + * Method returns requested entity primary key by specifying its primary key (either virtual or real). + */ + public int translateToEntityPrimaryKey(int primaryKey) { + if (this.entityReferencePkSequence > 0) { + final EntityReferenceContract referencedEntity = this.entityReferencePkIndex.get(primaryKey); + return referencedEntity == null ? primaryKey : referencedEntity.getPrimaryKey(); + } else { + return primaryKey; + } + } + /** * Method returns requested {@link EntityReference} by specifying its primary key (either virtual or real). */ diff --git a/evita_engine/src/main/java/io/evitadb/core/query/QueryPlanner.java b/evita_engine/src/main/java/io/evitadb/core/query/QueryPlanner.java index a06c44c041..bf7b3f5f73 100644 --- a/evita_engine/src/main/java/io/evitadb/core/query/QueryPlanner.java +++ b/evita_engine/src/main/java/io/evitadb/core/query/QueryPlanner.java @@ -49,8 +49,10 @@ import io.evitadb.core.query.indexSelection.TargetIndexes; import io.evitadb.core.query.sort.CacheableSorter; import io.evitadb.core.query.sort.ConditionalSorter; +import io.evitadb.core.query.sort.NoSorter; import io.evitadb.core.query.sort.OrderByVisitor; import io.evitadb.core.query.sort.Sorter; +import io.evitadb.core.query.sort.primaryKey.TranslatedPrimaryKeySorter; import io.evitadb.index.CatalogIndex; import io.evitadb.index.EntityIndex; import io.evitadb.index.EntityIndexType; @@ -433,7 +435,7 @@ private static List createSorter( // in case of debug cached variant tree or the entity is not known, we cannot use cache here // and we need to retain original non-cached sorter final Sorter sorter = orderByVisitor.getSorter(); - builder.appendSorter(sorter); + builder.appendSorter(replaceNoSorterIfNecessary(queryContext, sorter)); } finally { if (multipleAlternatives) { queryContext.popStep(); @@ -456,7 +458,7 @@ private static List createSorter( queryContext, builder.getFilterFormula(), builder.getTargetIndexes(), builder.getPrefetchFormulaVisitor(), - replacedSorter + replaceNoSorterIfNecessary(queryContext, replacedSorter) ) ); } else { @@ -472,6 +474,26 @@ private static List createSorter( } } + /** + * This method replaces no sorter - which should always represent primary keys in ascending order - with the special + * implementation in case the entity is not known in the query. In such case the primary keys are translated + * different ids and those ids are translated back at the end of the query. Unfortunately the order of the translated + * keys might be different than the original order of the primary keys, so we need to sort them here according to + * their original primary keys order in ascending fashion. + * + * @param queryContext query context + * @param sorter identified sorter + * @return sorter in input or new implementation that ensures proper sorting by primary keys in ascending order + */ + @Nonnull + private static Sorter replaceNoSorterIfNecessary(@Nonnull QueryContext queryContext, @Nonnull Sorter sorter) { + if (sorter instanceof NoSorter && !queryContext.isEntityTypeKnown()) { + return TranslatedPrimaryKeySorter.INSTANCE; + } else { + return sorter; + } + } + /** * Method creates list of {@link ExtraResultProducer} implementations that fabricate requested extra data structures * that are somehow connected with the processed query taking existing formula and their memoized results into diff --git a/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/ReversedSorter.java b/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/ReversedSorter.java index a7394e6359..047a5f1157 100644 --- a/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/ReversedSorter.java +++ b/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/ReversedSorter.java @@ -73,8 +73,9 @@ public int sortAndSlice(@Nonnull QueryContext queryContext, @Nonnull Formula inp // copy the sorted data to result final int length = endIndex - startIndex; - System.arraycopy(filteredRecordIds, startIndex, result, peak, Math.min(result.length, length)); - return length; + final int newPeak = Math.min(filteredRecordIds.length, length); + System.arraycopy(filteredRecordIds, startIndex, result, peak, newPeak); + return newPeak; } } diff --git a/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/TranslatedPrimaryKeySorter.java b/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/TranslatedPrimaryKeySorter.java new file mode 100644 index 0000000000..70fdb2cca9 --- /dev/null +++ b/evita_engine/src/main/java/io/evitadb/core/query/sort/primaryKey/TranslatedPrimaryKeySorter.java @@ -0,0 +1,87 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * Licensed under the Business Source License, Version 1.1 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/FgForrest/evitaDB/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.evitadb.core.query.sort.primaryKey; + +import io.evitadb.core.query.QueryContext; +import io.evitadb.core.query.algebra.Formula; +import io.evitadb.core.query.sort.Sorter; +import io.evitadb.index.bitmap.Bitmap; +import io.evitadb.utils.ArrayUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * This sorter sorts translated primary keys according to original primary keys in ascending order. It is used with + * the special implementation in case the entity is not known in the query. In such case the primary keys are translated + * different ids and those ids are translated back at the end of the query. Unfortunately the order of the translated + * keys might be different than the original order of the primary keys, so we need to sort them here according to + * their original primary keys order in ascending fashion. + * + * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TranslatedPrimaryKeySorter implements Sorter { + public static final Sorter INSTANCE = new TranslatedPrimaryKeySorter(); + + @Nonnull + @Override + public Sorter cloneInstance() { + return INSTANCE; + } + + @Nonnull + @Override + public Sorter andThen(Sorter sorterForUnknownRecords) { + return INSTANCE; + } + + @Nullable + @Override + public Sorter getNextSorter() { + return null; + } + + @Override + public int sortAndSlice(@Nonnull QueryContext queryContext, @Nonnull Formula input, int startIndex, int endIndex, @Nonnull int[] result, int peak) { + final Bitmap translatedPrimaryKeysBitmap = input.compute(); + final int[] translatedPrimaryKeys = translatedPrimaryKeysBitmap.getArray(); + final int[] originalPrimaryKeys = translatedPrimaryKeysBitmap.stream().map(queryContext::translateToEntityPrimaryKey).toArray(); + // initialize order array + final int[] order = new int[originalPrimaryKeys.length]; + for (int i = 0; i < order.length; i++) { + order[i] = i; + } + + ArrayUtils.sortSecondAlongFirstArray(originalPrimaryKeys, order); + final int length = endIndex - startIndex; + for (int i = peak; i < translatedPrimaryKeys.length && i < length; i++) { + result[i] = translatedPrimaryKeys[order[i]]; + } + return peak + Math.min(translatedPrimaryKeys.length, length); + } + +} diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java index 372e526d76..29188679c2 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java @@ -41,7 +41,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * Handles requests for multiple unknown entities identified by their URLs or codes. @@ -72,8 +71,6 @@ protected EndpointResponse> doHandleRequest(@Nonnull Rest log.debug("Generated evitaDB query for unknown entity list fetch is `{}`.", query); final List entities = exchange.session().queryList(query, EntityClassifier.class); - log.info("Fetched list of unknown entities: {}", entities.stream().map(it -> it.getPrimaryKey().toString()).collect(Collectors.joining(", "))); - return new SuccessEndpointResponse<>(entities); }