From e29a7964e9ea2528acc159441c46eff844599a37 Mon Sep 17 00:00:00 2001 From: Aaron Morton Date: Tue, 16 Jul 2024 16:37:57 +1200 Subject: [PATCH] Table Refactoring - working insert and find This commit gets the insert and find working in limited cases. Find does not support any clauses, just returns everything. Insert expects the document to have a field called `key` The feature flag to enable tables has been renamed to fit convention, it is now `-Dstargate.tables.enabled=true` Full command to start quarkus in dev against HCD container would be: ./mvnw quarkus:dev -Dstargate.data-store.ignore-bridge=true \ -Dstargate.jsonapi.operations.vectorize-enabled=true \ -Dstargate.jsonapi.operations.database-config.local-datacenter=dc1 \ -Dquarkus.log.console.darken=2 -Dstargate.tables.enabled=true -Poffline This is the 3rd chunk of changes, it is built against the work in ajm/tables-chunk-2 branch. it pulls commits from the ajm/tables branch listed below: commit e35c78b5dcf21462670e258e6ccad45e7215bf4e Author: Aaron Morton Date: Thu Jul 11 13:40:20 2024 +1200 fmt fix commit 64f2993c70a2c9535d19f397cd939e873d2193b8 Author: Aaron Morton Date: Thu Jul 11 13:37:20 2024 +1200 fmt fix -> Skipped - merge commit <- commit d69d2c64d39ef92f3b09a857f3025bc8411fcc9f Merge: f7bad222 018680b5 Author: Aaron Morton Date: Thu Jul 11 13:31:19 2024 +1200 Merge branch 'main' into ajm/tables commit f7bad222cdc9e9196bdbfb30db86481de7f74fd4 Author: Aaron Morton Date: Thu Jul 11 11:52:49 2024 +1200 Test fixes for Integration tests ReadDocument now accepts an optional docID see comments in ReadAndUpdateOperation about how we create an upsert doc commit adb2fa9e7a08a536311a431e9e313b420b2b1d5a Author: Aaron Morton Date: Thu Jul 11 10:40:14 2024 +1200 clean for InsertAttempt comments up to standard, removed unused getRow() commit 4985a947debd109a2608b5c737b1b98790072bf1 Author: Tatu Saloranta Date: Wed Jul 10 15:24:26 2024 -0700 Replace FeatureFlags with Quarkus config setting (#1257) -> skipped - already in main <- commit 018680b529fb15ca1516a3a68868ee30c0860711 Author: Hazel Date: Wed Jul 10 18:06:48 2024 -0400 Follow up for PR #1251: Remove `Optional` and centralize validation (#1259) -> SKIPPED - NOT IN MAIN BUT SAME AS 9d977cde9b26bb7b4f9059c582d7a25308ab32ce below <- commit bd6be18e3e22aa66fca7e71e24576a08f753b752 Author: Hazel Date: Wed Jul 10 16:41:24 2024 -0400 Improve error message when EGW timeout (#1255) -> SKIPPED - NOT IN MAIN BUT SAME AS 197bfd2ddf0f9754291cee9adbf3bcd4f484ed92 below <- commit 33311d26a694b4a062a79fefc9cae14701a661f9 Author: Hazel Date: Wed Jul 10 15:12:45 2024 -0400 Small fix: Improve error message when not providing provider key through `x-embedding-api-key` (#1251) -> SKIPPED - NOT IN MAIN BUT SAME AS dd652c3913125c5c973f68010e528358f530b89d below <- commit 486977ffa258076ae18fab2a610ba6373292abb4 Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed Jul 10 11:44:53 2024 -0400 Use Stargate v2.1.0-BETA-13 (#1252) Co-authored-by: jeffreyscarpenter <12115970+jeffreyscarpenter@users.noreply.github.com> commit 611282abeaa6de8e00e9e9e1ffa396660d19d63f Author: Aaron Morton Date: Thu Jul 11 10:00:03 2024 +1200 Insert working, one and many - some Test failing -> skipped - already in main <- commit 9d977cde9b26bb7b4f9059c582d7a25308ab32ce Author: Hazel Date: Wed Jul 10 16:41:24 2024 -0400 Improve error message when EGW timeout (#1255) -> skipped - already in main <- commit 197bfd2ddf0f9754291cee9adbf3bcd4f484ed92 Author: Hazel Date: Wed Jul 10 15:12:45 2024 -0400 Small fix: Improve error message when not providing provider key through `x-embedding-api-key` (#1251) -> skipped - already in main <- commit dd652c3913125c5c973f68010e528358f530b89d Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed Jul 10 11:44:53 2024 -0400 Use Stargate v2.1.0-BETA-13 (#1252) Co-authored-by: jeffreyscarpenter <12115970+jeffreyscarpenter@users.noreply.github.com> commit 7e6e99dac5b42aa76b0e93d6e600e8140a821006 Author: Aaron Morton Date: Wed Jul 10 16:37:47 2024 +1200 refactor Insert results ready for tables commit 588b297718b2fe2ca07702c7afa66fd7114180df Author: Aaron Morton Date: Wed Jul 10 13:47:06 2024 +1200 Refactor ReadOperationPage for reuse with tables and added DocumentSource commit d62f95c4537b0adc997e58c945405d1925f30116 Author: Aaron Morton Date: Wed Jul 10 10:34:56 2024 +1200 Basic findOne and find for tables just does a select *, in place to find re-use with collection commands commit fbd09e9fe5a084cd34b7dad78c271aa6b11b3ae1 Author: Tatu Saloranta Date: Tue Jul 9 18:08:16 2024 -0700 Enable metadata access to "system" keyspace (for testing purposes) --- .../sgv2/jsonapi/config/ApiTablesConfig.java | 12 ++ .../sgv2/jsonapi/service/FeatureFlags.java | 7 - .../cqldriver/executor/NamespaceCache.java | 12 +- .../cqldriver/executor/SchemaCache.java | 6 +- .../operation/model/DocumentSource.java | 29 ++++ .../operation/model/InsertAttempt.java | 67 ++++++++ .../operation/model/InsertOperationPage.java | 159 ++++++++++++++++++ .../operation/model/ReadOperationPage.java | 52 ++++++ .../model/collections/ChainedComparator.java | 3 +- .../collections/CollectionInsertAttempt.java | 82 +++++++++ .../collections/CollectionReadOperation.java | 12 +- .../model/collections/DeleteOperation.java | 9 +- .../collections/DeleteOperationPage.java | 4 +- .../model/collections/FindOperation.java | 3 +- .../model/collections/InsertOperation.java | 103 +++--------- .../collections/InsertOperationPage.java | 126 -------------- .../collections/ReadAndUpdateOperation.java | 29 +++- .../model/collections/ReadDocument.java | 51 ++++-- .../model/collections/ReadOperationPage.java | 50 ------ .../model/tables/FindTableOperation.java | 84 +++++++++ .../model/tables/FindTableOperaton.java | 25 --- .../model/tables/InsertTableOperation.java | 94 +++++++++++ .../model/tables/TableInsertAttempt.java | 78 +++++++++ .../model/tables/TableMutationOperation.java | 10 +- .../model/tables/TableOperation.java | 13 +- .../model/tables/TableReadOperation.java | 13 +- .../model/tables/TableSchemaOperation.java | 10 +- .../model/impl/FindCommandResolver.java | 16 ++ .../model/impl/FindOneCommandResolver.java | 10 ++ .../model/impl/InsertManyCommandResolver.java | 32 +++- .../model/impl/InsertOneCommandResolver.java | 22 ++- .../service/shredding/DocRowIdentifer.java | 11 ++ .../service/shredding/WritableDocRow.java | 6 + .../service/shredding/model/DocumentId.java | 9 +- .../model/WritableShreddedDocument.java | 11 +- .../service/shredding/tables/RowId.java | 12 ++ .../service/shredding/tables/RowShredder.java | 68 ++++++++ .../shredding/tables/WriteableTableRow.java | 15 ++ src/main/resources/application.conf | 7 +- src/main/resources/application.yaml | 4 + .../stargate/sgv2/jsonapi/TestConstants.java | 10 ++ .../executor/NamespaceCacheTest.java | 10 +- .../model/ChainedComparatorTest.java | 72 ++++---- .../impl/UpdateManyCommandResolverTest.java | 3 +- 44 files changed, 1078 insertions(+), 383 deletions(-) create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java delete mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/FeatureFlags.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/DocumentSource.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertAttempt.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertOperationPage.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/ReadOperationPage.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionInsertAttempt.java delete mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperationPage.java delete mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadOperationPage.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperation.java delete mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperaton.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/InsertTableOperation.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableInsertAttempt.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/shredding/DocRowIdentifer.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/shredding/WritableDocRow.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowId.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowShredder.java create mode 100644 src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/WriteableTableRow.java diff --git a/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java b/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java new file mode 100644 index 0000000000..d4a423540e --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/config/ApiTablesConfig.java @@ -0,0 +1,12 @@ +package io.stargate.sgv2.jsonapi.config; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; + +/** Configuration mapping for API Tables feature. */ +@ConfigMapping(prefix = "stargate.tables") +public interface ApiTablesConfig { + /** Setting that determines if the API Tables feature is enabled. */ + @WithDefault("false") + boolean enabled(); +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/FeatureFlags.java b/src/main/java/io/stargate/sgv2/jsonapi/service/FeatureFlags.java deleted file mode 100644 index a3b2d1f996..0000000000 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/FeatureFlags.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.stargate.sgv2.jsonapi.service; - -/** Hack / temp class to have run time checking for if Tables are supported */ -public final class FeatureFlags { - - public static final boolean TABLES_SUPPORTED = Boolean.getBoolean("stargate.tables.supported"); -} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java index 24cdaf95cf..abb8e2af22 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCache.java @@ -8,7 +8,6 @@ import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo; import io.stargate.sgv2.jsonapi.exception.ErrorCode; import io.stargate.sgv2.jsonapi.exception.JsonApiException; -import io.stargate.sgv2.jsonapi.service.FeatureFlags; import io.stargate.sgv2.jsonapi.service.schema.model.JsonapiTableMatcher; import java.time.Duration; @@ -24,6 +23,8 @@ public class NamespaceCache { private final ObjectMapper objectMapper; + private final boolean apiTablesEnabled; + // TODO: move the settings to config // TODO: set the cache loader when creating the cache private static final long CACHE_TTL_SECONDS = 300; @@ -34,10 +35,15 @@ public class NamespaceCache { .maximumSize(CACHE_MAX_SIZE) .build(); - public NamespaceCache(String namespace, QueryExecutor queryExecutor, ObjectMapper objectMapper) { + public NamespaceCache( + String namespace, + boolean apiTablesEnabled, + QueryExecutor queryExecutor, + ObjectMapper objectMapper) { this.namespace = namespace; this.queryExecutor = queryExecutor; this.objectMapper = objectMapper; + this.apiTablesEnabled = apiTablesEnabled; } protected Uni getSchemaObject( @@ -130,7 +136,7 @@ private Uni loadSchemaObject( optionalTable.get(), objectMapper); } - if (FeatureFlags.TABLES_SUPPORTED) { + if (apiTablesEnabled) { return new TableSchemaObject(namespace, collectionName); } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java index 864087a9a8..b4f602833d 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/SchemaCache.java @@ -9,6 +9,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import io.smallrye.mutiny.Uni; import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo; +import io.stargate.sgv2.jsonapi.config.ApiTablesConfig; import io.stargate.sgv2.jsonapi.config.OperationsConfig; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -25,6 +26,8 @@ public class SchemaCache { @Inject OperationsConfig operationsConfig; + @Inject ApiTablesConfig apiTablesConfig; + // TODO: The size of the cache should be in configuration. // TODO: set the cache loader when creating the cache private final Cache schemaCache = @@ -65,7 +68,8 @@ public void evictCollectionSettingCacheEntry( } private NamespaceCache addNamespaceCache(CacheKey cacheKey) { - return new NamespaceCache(cacheKey.namespace(), queryExecutor, objectMapper); + return new NamespaceCache( + cacheKey.namespace(), apiTablesConfig.enabled(), queryExecutor, objectMapper); } /** diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/DocumentSource.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/DocumentSource.java new file mode 100644 index 0000000000..8c7479455d --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/DocumentSource.java @@ -0,0 +1,29 @@ +package io.stargate.sgv2.jsonapi.service.operation.model; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.function.Supplier; + +/** + * A source of document that have been read from the DB, the document could be from a collection or + * a table. + * + *

The implementation may create the {@link JsonNode} lazy when it is called, or create and + * cache. + * + *

Note: this does not implement {@link Supplier} because there are times we use these and + * subclasses in a Uni chain which will see the supplier and call it, when what we really want to do + * is pass the instance along. + */ +@FunctionalInterface +public interface DocumentSource { + + /** + * Get the document as a JsonNode + * + *

Note to implementers, this method must always return a document. It is not valid to return + * null. + * + * @return the document as a JsonNode + */ + JsonNode get(); +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertAttempt.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertAttempt.java new file mode 100644 index 0000000000..f30c98102a --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertAttempt.java @@ -0,0 +1,67 @@ +package io.stargate.sgv2.jsonapi.service.operation.model; + +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; +import java.util.Optional; + +/** + * Container for an individual Document or Row insertion attempy. + * + *

Tracks the original input position; document (if available), its id (if available) and + * possible processing error. + * + *

Information will be needed to build responses, including the optional detail response see + * {@link InsertOperationPage} + * + *

Is {@link Comparable} so that the attempts can be re-sorted into the order provided in the + * user request, compares based on the {@link #position()} + */ +public interface InsertAttempt extends Comparable { + + /** + * The zero based position of the document or row in the request from the user. + * + * @return integer position + */ + int position(); + + /** + * The document _id or the row primary key, if known, used to build the response that includes the + * Id's of the documents / rows that were successfully inserted or failed. + * + *

Optional as there may be times when the input document / row could not be parsed to get the + * ID. And separate to having the doc / row shreddded because we may have the id (such as when + * creating a new document _id sever side) but were not able to shred the document / row. + * + * @return The {@link DocRowIdentifer} that identifies the document or row by ID + */ + Optional docRowID(); + + /** + * The first error that happened trying to run this insert. + * + * @return + */ + Optional failure(); + + /** + * Updates the attempt with an error that happened when trying to process the insert. + * + *

Implmentations must only remember the first error that happened. + * + * @param failure An error that happened when trying to process the insert. + * @return Return the updated {@link InsertAttempt}, must be the same instance the method was + * called on. + */ + InsertAttempt maybeAddFailure(Throwable failure); + + /** + * Compares the position of this attempt to another. + * + * @param other the object to be compared. + * @return Result of {@link Integer#compare(int, int)} + */ + @Override + default int compareTo(InsertAttempt other) { + return Integer.compare(position(), other.position()); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertOperationPage.java new file mode 100644 index 0000000000..fad3569524 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/InsertOperationPage.java @@ -0,0 +1,159 @@ +package io.stargate.sgv2.jsonapi.service.operation.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; +import io.stargate.sgv2.jsonapi.api.model.command.CommandStatus; +import io.stargate.sgv2.jsonapi.exception.mappers.ThrowableToErrorMapper; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; +import java.util.*; +import java.util.function.Supplier; + +/** + * The internal to insert operation results, keeping ids of successfully and not-successfully + * inserted documents. + * + *

Can serve as an aggregator, using the {@link #aggregate} function. TODO: AARON DOCS + * + * @param allInsertions Attempted insertions + * @param returnDocumentResponses Whether to return detailed document responses + * @param successfulInsertions Successfully inserted documents, NOTE: this list is mutated after + * creation + * @param failedInsertions Failed insertions NOTE: this list is mutated after creation + */ +public class InsertOperationPage implements Supplier { + + // TODO: AARON changed from a record because the successfulInsertions and failedInsertions were + // setable in the ctor + // but they have to be empty mutable lists + private final List allInsertions; + private final boolean returnDocumentResponses; + + // The success and failed lists are mutable and are used to build the response + // they do not use the ? wild card because they are always added to via {@link #aggregate} which + // wants to accept only of the InsertAttempt interface + private final List successfulInsertions; + private final List failedInsertions; + + public InsertOperationPage( + List allAttemptedInsertions, boolean returnDocumentResponses) { + this.allInsertions = List.copyOf(allAttemptedInsertions); + this.returnDocumentResponses = returnDocumentResponses; + + this.successfulInsertions = new ArrayList<>(allAttemptedInsertions.size()); + this.failedInsertions = new ArrayList<>(allAttemptedInsertions.size()); + } + + enum InsertionStatus { + OK, + ERROR, + SKIPPED + } + + // TODO AARON - I think this is for the document responses, confirm + @JsonPropertyOrder({"_id", "status", "errorsIdx"}) + @JsonInclude(JsonInclude.Include.NON_NULL) + record InsertionResult(DocRowIdentifer _id, InsertionStatus status, Integer errorsIdx) {} + + /** {@inheritDoc} */ + @Override + public CommandResult get() { + // Sort on the insert position to rebuild the order we of the documents from the insert. + // used for both legacy and new style output + Collections.sort(failedInsertions); + // TODO AARON used to only sort the success list when not returning detaile responses, check OK + Collections.sort(successfulInsertions); + + if (!returnDocumentResponses) { // legacy output, limited to ids, error messages + List errors = + failedInsertions.isEmpty() + ? null + : failedInsertions.stream().map(InsertOperationPage::getOldStyleError).toList(); + + // Note: See DocRowIdentifer, it has an attribute that will be called for JSON serialization + List insertedIds = + successfulInsertions.stream() + .map(InsertAttempt::docRowID) + .map(Optional::orElseThrow) + .toList(); + return new CommandResult(null, Map.of(CommandStatus.INSERTED_IDS, insertedIds), errors); + } + + // UPTO THIS AARON + + // New style output: detailed responses. + InsertionResult[] results = new InsertionResult[allInsertions.size()]; + List errors = new ArrayList<>(); + + // Results array filled in order: first successful insertions + for (InsertAttempt okInsertion : successfulInsertions) { + results[okInsertion.position()] = + new InsertionResult(okInsertion.docRowID().orElseThrow(), InsertionStatus.OK, null); + } + // Second: failed insertions; output in order of insertion + for (InsertAttempt failedInsertion : failedInsertions) { + // TODO AARON - confirm the null handling in the getError + CommandResult.Error error = getError(failedInsertion.failure().orElse(null)); + + // We want to avoid adding the same error multiple times, so we keep track of the index: + // either one exists, use it; or if not, add it and use the new index. + int errorIdx = errors.indexOf(error); + if (errorIdx < 0) { // new non-dup error; add it + errorIdx = errors.size(); // will be appended at the end + errors.add(error); + } + results[failedInsertion.position()] = + new InsertionResult( + failedInsertion.docRowID().orElseThrow(), InsertionStatus.ERROR, errorIdx); + } + + // And third, if any, skipped insertions; those that were not attempted (f.ex due + // to failure for ordered inserts) + for (int i = 0; i < results.length; i++) { + if (null == results[i]) { + results[i] = + new InsertionResult( + allInsertions.get(i).docRowID().orElseThrow(), InsertionStatus.SKIPPED, null); + } + } + return new CommandResult( + null, Map.of(CommandStatus.DOCUMENT_RESPONSES, Arrays.asList(results)), errors); + } + + private static CommandResult.Error getOldStyleError(InsertAttempt insertAttempt) { + String message = + "Failed to insert document with _id %s: %s" + .formatted( + insertAttempt.docRowID().orElseThrow(), + insertAttempt + .failure() + .map(Throwable::getMessage) + .orElse("InsertAttempt failure was null.")); + + /// TODO: confirm the null hanlding in the getMapperWithMessageFunction + // passing null is what would have happened before changing to optional + return ThrowableToErrorMapper.getMapperWithMessageFunction() + .apply(insertAttempt.failure().orElse(null), message); + } + + private static CommandResult.Error getError(Throwable throwable) { + // TODO AARON - confirm we have two different error message paths + return ThrowableToErrorMapper.getMapperWithMessageFunction() + .apply(throwable, throwable.getMessage()); + } + + /** + * Aggregates the result of the insert operation into this object. + * + * @param insertion Document insertion attempt + */ + public void aggregate(InsertAttempt insertion) { + // TODO: AARON: confirm this should not add to the allInsertions list. It would seem better if + // it did + insertion + .failure() + .ifPresentOrElse( + throwable -> failedInsertions.add(insertion), + () -> successfulInsertions.add(insertion)); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/ReadOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/ReadOperationPage.java new file mode 100644 index 0000000000..34d02cd5c5 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/ReadOperationPage.java @@ -0,0 +1,52 @@ +package io.stargate.sgv2.jsonapi.service.operation.model; + +import com.fasterxml.jackson.databind.JsonNode; +import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; +import io.stargate.sgv2.jsonapi.api.model.command.CommandStatus; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * Holds the documents froma read operation to create the {@link CommandResult} + * + * @param documentSources The source documents to be included the results, may be empty but never + * null. If singleResponse only the first {@link DocumentSource} will be used. + * @param singleResponse if the response data should be a single document response. Needed in + * addition to the list because we need to know if we got zero results for a findOne or a + * findMany. + * @param nextPageState pagination state, maybe null + * @param includeSortVector if the response data should include the sort vector, used in conjunction + * with the vector param so we always include the {@link CommandStatus#SORT_VECTOR} status when + * set. + * @param vector sort clause vector, no checking done. + */ +public record ReadOperationPage( + List documentSources, + boolean singleResponse, + String nextPageState, + boolean includeSortVector, + float[] vector) + implements Supplier { + + @Override + public CommandResult get() { + + Map status = + includeSortVector ? Collections.singletonMap(CommandStatus.SORT_VECTOR, vector) : null; + + List jsonDocs = + documentSources.stream() + .limit(singleResponse ? 1 : Long.MAX_VALUE) + .map(DocumentSource::get) + .toList(); + + var responseData = + singleResponse + ? new CommandResult.SingleResponseData(jsonDocs.isEmpty() ? null : jsonDocs.get(0)) + : new CommandResult.MultiResponseData(jsonDocs, nextPageState); + + return new CommandResult(responseData, status); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ChainedComparator.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ChainedComparator.java index 43822e002f..00b1605b8b 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ChainedComparator.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ChainedComparator.java @@ -24,6 +24,7 @@ public int compare(ReadDocument o1, ReadDocument o2) { // This needs to be done to maintain the relative ordering of document whose sort fields values // are same. return JsonNodeComparator.ascending() - .compare(o1.id().asJson(objectMapper), o2.id().asJson(objectMapper)); + .compare( + o1.id().orElseThrow().asJson(objectMapper), o2.id().orElseThrow().asJson(objectMapper)); } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionInsertAttempt.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionInsertAttempt.java new file mode 100644 index 0000000000..2c5a5bdd4e --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionInsertAttempt.java @@ -0,0 +1,82 @@ +package io.stargate.sgv2.jsonapi.service.operation.model.collections; + +import io.stargate.sgv2.jsonapi.service.operation.model.InsertAttempt; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; +import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; +import io.stargate.sgv2.jsonapi.service.shredding.model.WritableShreddedDocument; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Container for an individual Document insertion attempt: used to keep track of the original input + * position; document (if available), its id (if available) and possible processing error. + * Information will be needed to build optional detail response (returnDocumentResponses). + */ +public class CollectionInsertAttempt implements InsertAttempt { + + // TODO: AARON moved out of the inner class to make sure the encapsulation was correct, previously + // InsertOperation + // was using private fields from this class + + private final int position; + public final WritableShreddedDocument document; + private final DocumentId documentId; + + private Throwable failure; + + public CollectionInsertAttempt(int position, DocumentId documentId, Throwable failure) { + this.position = position; + // TODO confirm why the document is allowed to be null + this.document = null; + this.documentId = documentId; + this.failure = failure; + } + + private CollectionInsertAttempt(int position, WritableShreddedDocument document) { + this.position = position; + this.document = document; + this.documentId = document.id(); + } + + public static CollectionInsertAttempt from(int position, WritableShreddedDocument document) { + return new CollectionInsertAttempt(position, document); + } + + public static List from(List documents) { + final int count = documents.size(); + List result = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + result.add(from(i, documents.get(i))); + } + return result; + } + + @Override + public int position() { + return position; + } + + @Override + public Optional docRowID() { + return Optional.ofNullable(documentId); + } + + @Override + public Optional failure() { + return Optional.ofNullable(failure); + } + + @Override + public InsertAttempt maybeAddFailure(Throwable failure) { + if (this.failure == null) { + this.failure = failure; + } + return this; + } + + public boolean hasVectorValues() { + // TODO: AARON work out if we need hasVecotrs int he base on the base + return (document != null) && (document.queryVectorValues() != null); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionReadOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionReadOperation.java index acff9c25d7..b1f35bcd60 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionReadOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/CollectionReadOperation.java @@ -117,6 +117,7 @@ default Uni findDocument( JsonNode root = readDocument ? objectMapper.readTree(row.getString(2)) : null; if (root != null) { // create metrics + // TODO Use the column names! jsonProcessingMetricsReporter.reportJsonReadBytesMetrics( commandName, row.getString(2).length()); @@ -346,9 +347,16 @@ default Uni findOrderDocument( subList.stream() .map( readDoc -> { - JsonNode data = readDoc.docJsonValue().get(); + JsonNode data = readDoc.docSupplier().get(); projection.applyProjection(data); - return ReadDocument.from(readDoc.id(), readDoc.txnId(), data); + // TODO AARON below is the old code, why do we need to create a new + // obj because applyProjection mutates the document ? + // also, if this doc was from upsert the original ReadDocument obj mayn + // ot have the doc ID + // if there was not one in the filter. + // orig return ReadDocument.from(readDoc.id(), readDoc.txnId(), data); + return ReadDocument.from( + readDoc.id().orElse(null), readDoc.txnId().orElse(null), data); }) .collect(Collectors.toList()); return new FindResponse(responseDocuments, null); diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperation.java index 2d8420e25b..04098859f5 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperation.java @@ -158,7 +158,7 @@ public Uni> execute( } private ReadDocument applyProjection(ReadDocument document) { - resultProjection().applyProjection(document.document()); + resultProjection().applyProjection(document.get()); return document; } @@ -236,7 +236,7 @@ private Uni readDocumentAgain( dataApiRequestInfo, queryExecutor, null, - new IDCollectionFilter(IDCollectionFilter.Operator.EQ, prevReadDoc.id())) + new IDCollectionFilter(IDCollectionFilter.Operator.EQ, prevReadDoc.id().orElseThrow())) .onItem() .transform( response -> { @@ -251,7 +251,10 @@ private Uni readDocumentAgain( private static SimpleStatement bindDeleteQuery(String query, ReadDocument doc) { SimpleStatement deleteStatement = - SimpleStatement.newInstance(query, CQLBindValues.getDocumentIdValue(doc.id()), doc.txnId()); + SimpleStatement.newInstance( + query, + CQLBindValues.getDocumentIdValue(doc.id().orElseThrow()), + doc.txnId().orElse(null)); return deleteStatement; } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperationPage.java index fb7be8a192..0b257d7fb2 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperationPage.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/DeleteOperationPage.java @@ -48,7 +48,7 @@ public CommandResult get() { deletedInformation.forEach( deletedData -> { if (deletedData.getItem1() && returnDocument()) { - deletedDoc.add(deletedData.getItem3().document()); + deletedDoc.add(deletedData.getItem3().get()); } if (deletedData.getItem2() != null) { String key = ExceptionUtil.getThrowableGroupingKey(deletedData.getItem2()); @@ -66,7 +66,7 @@ public CommandResult get() { groupedErrorDeletes.get(key); final List documentIds = deletedDocuments.stream() - .map(deletes -> deletes.getItem3().id()) + .map(deletes -> deletes.getItem3().id().orElseThrow()) .collect(Collectors.toList()); errors.add( ExceptionUtil.getError( diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/FindOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/FindOperation.java index 91bbf201fd..6d7c7265d7 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/FindOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/FindOperation.java @@ -17,6 +17,7 @@ import io.stargate.sgv2.jsonapi.service.cql.builder.QueryBuilder; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor; +import io.stargate.sgv2.jsonapi.service.operation.model.ReadOperationPage; import io.stargate.sgv2.jsonapi.service.operation.model.builder.BuiltCondition; import io.stargate.sgv2.jsonapi.service.operation.model.filters.DBFilterBase; import io.stargate.sgv2.jsonapi.service.operation.model.filters.collection.CollectionFilter; @@ -322,7 +323,7 @@ public Uni> execute( .jsonProcessingMetricsReporter() .reportJsonReadDocsMetrics(commandContext().commandName(), docs.docs().size()); return new ReadOperationPage( - docs.docs(), docs.pageState(), singleResponse, includeSortVector(), vector()); + docs.docs(), singleResponse, docs.pageState(), includeSortVector(), vector()); }); } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperation.java index 2431958c1d..f6b2c87568 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperation.java @@ -11,12 +11,10 @@ import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor; import io.stargate.sgv2.jsonapi.service.cqldriver.serializer.CQLBindValues; +import io.stargate.sgv2.jsonapi.service.operation.model.InsertOperationPage; import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; import io.stargate.sgv2.jsonapi.service.shredding.model.WritableShreddedDocument; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.Supplier; /** @@ -28,68 +26,12 @@ */ public record InsertOperation( CommandContext commandContext, - List insertions, + List insertions, boolean ordered, boolean offlineMode, boolean returnDocumentResponses) implements CollectionModifyOperation { - /** - * Container for an individual Document insertion attempt: used to keep track of the original - * input position; document (if available), its id (if available) and possible processing error. - * Information will be needed to build optional detail response (returnDocumentResponses). - */ - public static class InsertAttempt implements Comparable { - public final int position; - - public final WritableShreddedDocument document; - public final DocumentId documentId; - - public Throwable failure; - - public InsertAttempt(int position, DocumentId documentId, Throwable failure) { - this.position = position; - this.document = null; - this.documentId = documentId; - this.failure = failure; - } - - private InsertAttempt(int position, WritableShreddedDocument document) { - this.position = position; - this.document = document; - this.documentId = document.id(); - } - - public static InsertAttempt from(int position, WritableShreddedDocument document) { - return new InsertAttempt(position, document); - } - - public static List from(List documents) { - final int count = documents.size(); - List result = new ArrayList<>(count); - for (int i = 0; i < count; ++i) { - result.add(from(i, documents.get(i))); - } - return result; - } - - public InsertAttempt addFailure(Throwable failure) { - if (failure != null) { - this.failure = failure; - } - return this; - } - - public boolean hasVectorValues() { - return (document != null) && (document.queryVectorValues() != null); - } - - @Override - public int compareTo(InsertOperation.InsertAttempt o) { - return Integer.compare(position, o.position); - } - } - public static InsertOperation create( CommandContext commandContext, List documents, @@ -98,7 +40,7 @@ public static InsertOperation create( boolean returnDocumentResponses) { return new InsertOperation( commandContext, - InsertAttempt.from(documents), + CollectionInsertAttempt.from(documents), ordered, offlineMode, returnDocumentResponses); @@ -110,14 +52,18 @@ public static InsertOperation create( boolean ordered, boolean returnDocumentResponses) { return new InsertOperation( - commandContext, InsertAttempt.from(documents), ordered, false, returnDocumentResponses); + commandContext, + CollectionInsertAttempt.from(documents), + ordered, + false, + returnDocumentResponses); } public static InsertOperation create( CommandContext commandContext, WritableShreddedDocument document) { return new InsertOperation( commandContext, - Collections.singletonList(InsertAttempt.from(0, document)), + Collections.singletonList(CollectionInsertAttempt.from(0, document)), false, false, false); @@ -152,7 +98,7 @@ private Uni> insertOrdered( DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor, boolean vectorEnabled, - List insertions) { + List insertions) { // build query once final String query = buildInsertQuery(vectorEnabled); @@ -174,20 +120,21 @@ private Uni> insertOrdered( // wrap item and failure // the collection can decide how to react on failure .onItemOrFailure() - .transform((id, t) -> insertion.addFailure(t))) + .transform((id, t) -> insertion.maybeAddFailure(t))) .concatenate(false) // if no failures reduce to the op page .collect() .in( () -> new InsertOperationPage(insertions, returnDocumentResponses()), - (agg, in) -> { - Throwable failure = in.failure; - agg.aggregate(in); - - if (failure != null) { - throw new FailFastInsertException(agg, failure); - } + (insertPage, insertAttempt) -> { + insertPage.aggregate(insertAttempt); + insertAttempt + .failure() + .ifPresent( + failure -> { + throw new FailFastInsertException(insertPage, failure); + }); }) // in case upstream propagated FailFastInsertException @@ -209,7 +156,7 @@ private Uni> insertUnordered( DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor, boolean vectorEnabled, - List insertions) { + List insertions) { // build query once String query = buildInsertQuery(vectorEnabled); return Multi.createFrom() @@ -228,7 +175,7 @@ private Uni> insertUnordered( offlineMode) // handle errors fail silent mode .onItemOrFailure() - .transform((id, t) -> insertion.addFailure(t))) + .transform((id, t) -> insertion.maybeAddFailure(t))) // then reduce here .collect() .in( @@ -245,12 +192,12 @@ private static Uni insertDocument( DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor, String query, - InsertAttempt insertion, + CollectionInsertAttempt insertion, boolean vectorEnabled, boolean offlineMode) { // First things first: did we already fail? If so, propagate - if (insertion.failure != null) { - return Uni.createFrom().failure(insertion.failure); + if (insertion.failure().isPresent()) { + return Uni.createFrom().failure(insertion.failure().get()); } // bind and execute diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperationPage.java deleted file mode 100644 index 2500a691b3..0000000000 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/InsertOperationPage.java +++ /dev/null @@ -1,126 +0,0 @@ -package io.stargate.sgv2.jsonapi.service.operation.model.collections; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; -import io.stargate.sgv2.jsonapi.api.model.command.CommandStatus; -import io.stargate.sgv2.jsonapi.exception.mappers.ThrowableToErrorMapper; -import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -/** - * The internal to insert operation results, keeping ids of successfully and not-successfully - * inserted documents. - * - *

Can serve as an aggregator, using the {@link #aggregate} function. - * - * @param allInsertions Attempted insertions - */ -public record InsertOperationPage( - List allInsertions, - boolean returnDocumentResponses, - List successfulInsertions, - List failedInsertions) - implements Supplier { - enum InsertionStatus { - OK, - ERROR, - SKIPPED - } - - @JsonPropertyOrder({"_id", "status", "errorsIdx"}) - @JsonInclude(JsonInclude.Include.NON_NULL) - record InsertionResult(DocumentId _id, InsertionStatus status, Integer errorsIdx) {} - - public InsertOperationPage( - List allAttemptedInsertions, boolean returnDocumentResponses) { - this(allAttemptedInsertions, returnDocumentResponses, new ArrayList<>(), new ArrayList<>()); - } - - /** {@inheritDoc} */ - @Override - public CommandResult get() { - if (!returnDocumentResponses()) { // legacy output, limited to ids, error messages - List errors; - if (failedInsertions.isEmpty()) { - errors = null; - } else { - Collections.sort(failedInsertions); - errors = - failedInsertions.stream() - .map(insertion -> getError(insertion.documentId, insertion.failure)) - .toList(); - } - // Old style, simple ids: - Collections.sort(successfulInsertions); - List insertedIds = - successfulInsertions.stream().map(insertion -> insertion.documentId).toList(); - return new CommandResult(null, Map.of(CommandStatus.INSERTED_IDS, insertedIds), errors); - } - - // New style output: detailed responses. - InsertionResult[] results = new InsertionResult[allInsertions().size()]; - List errors = new ArrayList<>(); - - // Results array filled in order: first successful insertions - for (InsertOperation.InsertAttempt okInsertion : successfulInsertions) { - results[okInsertion.position] = - new InsertionResult(okInsertion.documentId, InsertionStatus.OK, null); - } - // Second: failed insertions; output in order of insertion - Collections.sort(failedInsertions); - for (InsertOperation.InsertAttempt failedInsertion : failedInsertions) { - Throwable throwable = failedInsertion.failure; - CommandResult.Error error = getError(throwable); - - // We want to avoid adding the same error multiple times, so we keep track of the index: - // either one exists, use it; or if not, add it and use the new index. - int errorIdx = errors.indexOf(error); - if (errorIdx < 0) { // new non-dup error; add it - errorIdx = errors.size(); // will be appended at the end - errors.add(error); - } - results[failedInsertion.position] = - new InsertionResult(failedInsertion.documentId, InsertionStatus.ERROR, errorIdx); - } - // And third, if any, skipped insertions; those that were not attempted (f.ex due - // to failure for ordered inserts) - for (int i = 0; i < results.length; i++) { - if (null == results[i]) { - results[i] = - new InsertionResult(allInsertions.get(i).documentId, InsertionStatus.SKIPPED, null); - } - } - return new CommandResult( - null, Map.of(CommandStatus.DOCUMENT_RESPONSES, Arrays.asList(results)), errors); - } - - private static CommandResult.Error getError(DocumentId documentId, Throwable throwable) { - String message = - "Failed to insert document with _id %s: %s".formatted(documentId, throwable.getMessage()); - return ThrowableToErrorMapper.getMapperWithMessageFunction().apply(throwable, message); - } - - private static CommandResult.Error getError(Throwable throwable) { - return ThrowableToErrorMapper.getMapperWithMessageFunction() - .apply(throwable, throwable.getMessage()); - } - - /** - * Aggregates the result of the insert operation into this object. - * - * @param insertion Document insertion attempt - */ - public void aggregate(InsertOperation.InsertAttempt insertion) { - if (insertion.failure == null) { - successfulInsertions.add(insertion); - } else { - failedInsertions.add(insertion); - } - } -} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadAndUpdateOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadAndUpdateOperation.java index cdf551fa42..94137ce398 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadAndUpdateOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadAndUpdateOperation.java @@ -69,6 +69,15 @@ public Uni> execute( pageStateReference.set(findResponse.pageState()); final List docs = findResponse.docs(); if (upsert() && docs.size() == 0 && matchedCount.get() == 0) { + // TODO: creating the new document here, with the defaults from the filter, makes it + // harder because + // the new document created here may nto have an _id if there was none in the + // filter. A better approach + // may be to have the documentUpdater create the upsert document totally in once + // place. Currently creating the + // upsert document is in multiple places. To do this we would create + // UpdateOperations from the filter and + // give them to the document updated when it is created. return Multi.createFrom().item(findOperation().getNewDocument()); } else { matchedCount.addAndGet(docs.size()); @@ -109,7 +118,7 @@ public Uni> execute( .recoverWithItem( error -> { return new UpdatedDocument( - readDocument.id(), false, null, error); + readDocument.id().orElseThrow(), false, null, error); }); })) .collect() @@ -141,7 +150,6 @@ private Uni processUpdate( AtomicInteger modifiedCount) { return Uni.createFrom() .item(document) - // perform update operation and save only if data is modified. .flatMap( readDocument -> { @@ -151,13 +159,13 @@ private Uni processUpdate( } // upsert if we have no transaction if before - boolean upsert = readDocument.txnId() == null; - JsonNode originalDocument = upsert ? null : readDocument.document(); + boolean upsert = readDocument.txnId().isEmpty(); + JsonNode originalDocument = upsert ? null : readDocument.get(); // apply document updates // if no changes return null item DocumentUpdater.DocumentUpdaterResponse documentUpdaterResponse = - documentUpdater().apply(readDocument.document().deepCopy(), upsert); + documentUpdater().apply(readDocument.get().deepCopy(), upsert); // In case no change to document and not an upsert document, short circuit and return if (!documentUpdaterResponse.modified() && !upsert) { @@ -165,7 +173,9 @@ private Uni processUpdate( if (returnDocumentInResponse) { resultProjection.applyProjection(originalDocument); return Uni.createFrom() - .item(new UpdatedDocument(readDocument.id(), upsert, originalDocument, null)); + .item( + new UpdatedDocument( + readDocument.id().orElseThrow(), upsert, originalDocument, null)); } else { return Uni.createFrom().nullItem(); } @@ -176,7 +186,10 @@ private Uni processUpdate( .shred( commandContext(), documentUpdaterResponse.document(), - readDocument.txnId()); + readDocument + .txnId() + .orElse(null) // will be empty when this is a upsert'd doc + ); // Have to do this because shredder adds _id field to the document if it doesn't exist JsonNode updatedDocument = writableShreddedDocument.docJsonNode(); @@ -330,7 +343,7 @@ private Uni readDocumentAgain( dataApiRequestInfo, queryExecutor, null, - new IDCollectionFilter(IDCollectionFilter.Operator.EQ, prevReadDoc.id())) + new IDCollectionFilter(IDCollectionFilter.Operator.EQ, prevReadDoc.id().orElseThrow())) .onItem() .transform( response -> { diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadDocument.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadDocument.java index 1f2d130fa6..9418be472f 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadDocument.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadDocument.java @@ -1,8 +1,11 @@ package io.stargate.sgv2.jsonapi.service.operation.model.collections; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Preconditions; +import io.stargate.sgv2.jsonapi.service.operation.model.DocumentSource; import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; @@ -10,24 +13,50 @@ * Represents a document read from the database * * @param id Document Id identifying the document - * @param txnId Unique UUID resenting point in time of a document, used for LWT transactions - * @param document JsonNode representation of the document + * @param txnId Unique UUID resenting point in time of a document, used for LWT transactions. This + * will be missing when the Document was created for an upsert, and there is code in the {@link + * ReadAndUpdateOperation} that uses this as the market. Optuonal is used to allow for the case + * where the document is from upsert + * @param docSupplier JsonNode representation of the document * @param sortColumns List Serialized sort column value - * @param docJsonValue Grpc column value for doc_json. */ public record ReadDocument( - DocumentId id, - UUID txnId, - JsonNode document, - List sortColumns, - Supplier docJsonValue) { + Optional id, + java.util.Optional txnId, + Supplier docSupplier, + List sortColumns) + implements DocumentSource { + + /// TODO AARON - comments + + public ReadDocument { + Preconditions.checkNotNull(txnId, "txnId cannot be null"); + Preconditions.checkNotNull(docSupplier, "docSupplier cannot be null"); + Preconditions.checkNotNull(sortColumns, "sortColumns cannot be null"); + } public static ReadDocument from(DocumentId id, UUID txnId, JsonNode document) { - return new ReadDocument(id, txnId, document, null, null); + return new ReadDocument( + Optional.ofNullable(id), Optional.ofNullable(txnId), () -> document, List.of()); } public static ReadDocument from( - DocumentId id, UUID txnId, Supplier docJsonValue, List sortColumns) { - return new ReadDocument(id, txnId, null, sortColumns, docJsonValue); + DocumentId id, UUID txnId, Supplier docSupplier, List sortColumns) { + return new ReadDocument( + Optional.ofNullable(id), Optional.ofNullable(txnId), docSupplier, sortColumns); + } + + // public ReadDocument replaceDocSupplier(Supplier docSupplier) { + // // TODO: the old code would let this happen + // return new ReadDocument(id, txnId, docSupplier, sortColumns); + // } + // + // public ReadDocument replaceDocSupplier(JsonNode doc) { + // return replaceDocSupplier(() -> doc); + // } + + @Override + public JsonNode get() { + return docSupplier.get(); } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadOperationPage.java deleted file mode 100644 index 87c14ddc74..0000000000 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/collections/ReadOperationPage.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.stargate.sgv2.jsonapi.service.operation.model.collections; - -import com.fasterxml.jackson.databind.JsonNode; -import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; -import io.stargate.sgv2.jsonapi.api.model.command.CommandStatus; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -/** - * FindOperation response implementing the {@link CommandResult}. - * - * @param docs list of documents - * @param pageState page state - * @param singleResponse if the response data should be a single document response - * @param includeSortVector if the response data should include the sort vector - * @param vector sort clause vector - */ -public record ReadOperationPage( - List docs, - String pageState, - boolean singleResponse, - boolean includeSortVector, - float[] vector) - implements Supplier { - @Override - public CommandResult get() { - Map status = null; - if (includeSortVector) { - // add sort vector to the response - status = Collections.singletonMap(CommandStatus.SORT_VECTOR, vector); - } - - // difference if we have single response target or not - if (singleResponse) { - // extract first document from docs with list size check - JsonNode jsonNode = docs.size() > 0 ? docs.get(0).document() : null; - return new CommandResult(new CommandResult.SingleResponseData(jsonNode), status); - } else { - // transform docs to json nodes - final List jsonNodes = new ArrayList<>(); - for (ReadDocument doc : docs) { - jsonNodes.add(doc.document()); - } - return new CommandResult(new CommandResult.MultiResponseData(jsonNodes, pageState), status); - } - } -} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperation.java new file mode 100644 index 0000000000..b1d12e2ad7 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperation.java @@ -0,0 +1,84 @@ +package io.stargate.sgv2.jsonapi.service.operation.model.tables; + +import com.datastax.oss.driver.api.core.cql.AsyncResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; +import io.smallrye.mutiny.Uni; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; +import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.LogicalExpression; +import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; +import io.stargate.sgv2.jsonapi.service.operation.model.DocumentSource; +import io.stargate.sgv2.jsonapi.service.operation.model.ReadOperationPage; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; +import org.apache.commons.lang3.NotImplementedException; + +public class FindTableOperation extends TableReadOperation { + + private final FindTableParams params; + + public FindTableOperation( + CommandContext commandContext, + LogicalExpression logicalExpression, + FindTableParams params) { + super(commandContext, logicalExpression); + + Preconditions.checkNotNull(params, "Params must not be null"); + this.params = params; + } + + @Override + public Uni> execute( + DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor) { + var sql = + "select JSON * from %s.%s limit %s;" + .formatted( + commandContext.schemaObject().name.keyspace(), + commandContext.schemaObject().name.table(), + params.limit()); + var statement = SimpleStatement.newInstance(sql); + + return queryExecutor + .executeRead(dataApiRequestInfo, statement, Optional.empty(), 100) + .onItem() + .transform(this::toReadOperationPage); + } + + private ReadOperationPage toReadOperationPage(AsyncResultSet resultSet) { + + var objectMapper = new ObjectMapper(); + + var docSources = + StreamSupport.stream(resultSet.currentPage().spliterator(), false) + .map( + row -> + (DocumentSource) + () -> { + try { + return objectMapper.readTree(row.getString("[json]")); + } catch (Exception e) { + throw new NotImplementedException("Bang " + e.getMessage()); + } + }) + .toList(); + + return new ReadOperationPage(docSources, params.isSingleResponse(), null, false, null); + } + + public record FindTableParams(int limit) { + + public FindTableParams(int limit) { + Preconditions.checkArgument(limit > 0, "Limit must be greater than 0"); + this.limit = limit; + } + + public boolean isSingleResponse() { + return limit == 1; + } + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperaton.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperaton.java deleted file mode 100644 index 3dba25aedc..0000000000 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/FindTableOperaton.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.stargate.sgv2.jsonapi.service.operation.model.tables; - -import io.smallrye.mutiny.Uni; -import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; -import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; -import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.LogicalExpression; -import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo; -import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; -import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor; -import java.util.function.Supplier; -import org.apache.commons.lang3.NotImplementedException; - -public class FindTableOperaton extends TableReadOperation { - - public FindTableOperaton( - CommandContext commandContext, LogicalExpression logicalExpression) { - super(commandContext, logicalExpression); - } - - @Override - public Uni> execute( - DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor) { - throw new NotImplementedException("Placeholder - work in progress"); - } -} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/InsertTableOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/InsertTableOperation.java new file mode 100644 index 0000000000..22860900a5 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/InsertTableOperation.java @@ -0,0 +1,94 @@ +package io.stargate.sgv2.jsonapi.service.operation.model.tables; + +import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.*; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.querybuilder.term.Term; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; +import io.stargate.sgv2.jsonapi.api.request.DataApiRequestInfo; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; +import io.stargate.sgv2.jsonapi.service.operation.model.InsertOperationPage; +import io.stargate.sgv2.jsonapi.service.shredding.tables.WriteableTableRow; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class InsertTableOperation extends TableMutationOperation { + + private final List insertAttempts; + + // TODO AARON JSON to start with, need a document object + public InsertTableOperation( + CommandContext commandContext, List insertAttempts) { + super(commandContext); + this.insertAttempts = List.copyOf(insertAttempts); + } + + @Override + public Uni> execute( + DataApiRequestInfo dataApiRequestInfo, QueryExecutor queryExecutor) { + + // TODO AARON - this is for unordered, copy from Collection InsertOperation insertUnordered + return Multi.createFrom() + .iterable(insertAttempts) + // merge to make it parallel + .onItem() + .transformToUniAndMerge( + insertion -> insertRow(dataApiRequestInfo, queryExecutor, insertion)) + // then reduce here + .collect() + .in(() -> new InsertOperationPage(insertAttempts, false), InsertOperationPage::aggregate) + // use object identity to resolve to Supplier + // TODO AARON - not sure what this is doing, original was .map(i -> i) + .map(Function.identity()); + } + + private Uni insertRow( + DataApiRequestInfo dataApiRequestInfo, + QueryExecutor queryExecutor, + TableInsertAttempt insertAttempt) { + + // First things first: did we already fail? If so, propagate + if (insertAttempt.failure().isPresent()) { + return Uni.createFrom().failure(insertAttempt.failure().get()); + } + + // bind and execute + var boundStatement = buildInsertStatement(queryExecutor, insertAttempt.row().orElseThrow()); + + // TODO: AARON What happens to errors here? + return queryExecutor + .executeWrite(dataApiRequestInfo, boundStatement) + .onItemOrFailure() + .transform( + (result, t) -> { + if (t != null) { + return (TableInsertAttempt) insertAttempt.maybeAddFailure(t); + } + // This is where to check result.wasApplied() if this was a LWT + return insertAttempt; + }) + .onItemOrFailure() + .transform((ia, throwable) -> (TableInsertAttempt) ia.maybeAddFailure(throwable)); + } + + private SimpleStatement buildInsertStatement(QueryExecutor queryExecutor, WriteableTableRow row) { + + Map colValues = + row.allColumnValues().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> literal(e.getValue()))); + + return insertInto( + commandContext.schemaObject().name.keyspace(), + commandContext.schemaObject().name.table()) + .valuesByIds(colValues) + .build(); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableInsertAttempt.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableInsertAttempt.java new file mode 100644 index 0000000000..d8da1a1d79 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableInsertAttempt.java @@ -0,0 +1,78 @@ +package io.stargate.sgv2.jsonapi.service.operation.model.tables; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Preconditions; +import io.stargate.sgv2.jsonapi.service.operation.model.InsertAttempt; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; +import io.stargate.sgv2.jsonapi.service.shredding.tables.RowId; +import io.stargate.sgv2.jsonapi.service.shredding.tables.RowShredder; +import io.stargate.sgv2.jsonapi.service.shredding.tables.WriteableTableRow; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +public class TableInsertAttempt implements InsertAttempt { + + private final int position; + private final RowId rowId; + private final WriteableTableRow row; + private Throwable failure; + + private TableInsertAttempt(int position, RowId rowId, WriteableTableRow row) { + this.position = position; + this.rowId = rowId; + this.row = row; + } + + public static List create(RowShredder shredder, JsonNode document) { + return create(shredder, List.of(document)); + } + + public static List create(RowShredder shredder, List documents) { + Preconditions.checkNotNull(shredder, "shredder cannot be null"); + Preconditions.checkNotNull(documents, "documents cannot be null"); + + return IntStream.range(0, documents.size()) + .mapToObj( + i -> { + WriteableTableRow row; + try { + row = shredder.shred(documents.get(i)); + } catch (Exception e) { + // TODO: need a shredding base excpetion to catch + // TODO: we need to get the row id, so we can return it in the response + return (TableInsertAttempt) + new TableInsertAttempt(i, null, null).maybeAddFailure(e); + } + return new TableInsertAttempt(i, row.id(), row); + }) + .toList(); + } + + public Optional row() { + return Optional.ofNullable(row); + } + + @Override + public int position() { + return position; + } + + @Override + public Optional docRowID() { + return Optional.ofNullable(rowId); + } + + @Override + public Optional failure() { + return Optional.ofNullable(failure); + } + + @Override + public InsertAttempt maybeAddFailure(Throwable failure) { + if (this.failure == null) { + this.failure = failure; + } + return this; + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableMutationOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableMutationOperation.java index 18edd2f0eb..16a7c104a1 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableMutationOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableMutationOperation.java @@ -1,4 +1,12 @@ package io.stargate.sgv2.jsonapi.service.operation.model.tables; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; + /** For now, a marker class / interface for operations that modify data in a table. */ -abstract class TableMutationOperation extends TableOperation {} +abstract class TableMutationOperation extends TableOperation { + + protected TableMutationOperation(CommandContext commandContext) { + super(commandContext); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableOperation.java index d67ff8d18d..b34216c405 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableOperation.java @@ -1,9 +1,20 @@ package io.stargate.sgv2.jsonapi.service.operation.model.tables; +import com.google.common.base.Preconditions; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; import io.stargate.sgv2.jsonapi.service.operation.model.Operation; /** * Base for any operations that works with CQL Tables with rows, rather than Collections of * Documents */ -abstract class TableOperation implements Operation {} +abstract class TableOperation implements Operation { + + protected final CommandContext commandContext; + + protected TableOperation(CommandContext commandContext) { + Preconditions.checkNotNull(commandContext, "commandContext cannot be null"); + this.commandContext = commandContext; + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableReadOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableReadOperation.java index 7662dcafaf..0f2ea64196 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableReadOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableReadOperation.java @@ -1,20 +1,19 @@ package io.stargate.sgv2.jsonapi.service.operation.model.tables; +import com.google.common.base.Preconditions; import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.LogicalExpression; -import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; -import java.util.Objects; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; /** For now, a marker class / interface for operations that read data in a table. */ abstract class TableReadOperation extends TableOperation { - protected final CommandContext commandContext; protected final LogicalExpression logicalExpression; public TableReadOperation( - CommandContext commandContext, LogicalExpression logicalExpression) { - this.commandContext = Objects.requireNonNull(commandContext, "commandContext cannot be null"); - this.logicalExpression = - Objects.requireNonNull(logicalExpression, "logicalExpression cannot be null"); + CommandContext commandContext, LogicalExpression logicalExpression) { + super(commandContext); + Preconditions.checkNotNull(logicalExpression, "logicalExpression cannot be null"); + this.logicalExpression = logicalExpression; } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableSchemaOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableSchemaOperation.java index 686234f061..cf746c258c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableSchemaOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/tables/TableSchemaOperation.java @@ -1,4 +1,12 @@ package io.stargate.sgv2.jsonapi.service.operation.model.tables; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; + /** For now, a marker class / interface for operations that modify schema */ -abstract class TableSchemaOperation extends TableOperation {} +abstract class TableSchemaOperation extends TableOperation { + + protected TableSchemaOperation(CommandContext commandContext) { + super(commandContext); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindCommandResolver.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindCommandResolver.java index c752120470..8b0e8e3c6f 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindCommandResolver.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindCommandResolver.java @@ -11,15 +11,18 @@ import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonApiMetricsConfig; import io.stargate.sgv2.jsonapi.config.OperationsConfig; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; import io.stargate.sgv2.jsonapi.service.operation.model.Operation; import io.stargate.sgv2.jsonapi.service.operation.model.collections.CollectionReadType; import io.stargate.sgv2.jsonapi.service.operation.model.collections.FindOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.FindTableOperation; import io.stargate.sgv2.jsonapi.service.resolver.model.CommandResolver; import io.stargate.sgv2.jsonapi.service.resolver.model.impl.matcher.FilterableResolver; import io.stargate.sgv2.jsonapi.util.SortClauseUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.List; +import java.util.Optional; /** Resolves the {@link FindOneCommand } */ @ApplicationScoped @@ -53,6 +56,19 @@ public Class getCommandClass() { return FindCommand.class; } + @Override + public Operation resolveTableCommand(CommandContext ctx, FindCommand command) { + // TODO AARON - make reusable code for getting the limit + + var limit = + Optional.ofNullable(command.options()) + .map(FindCommand.Options::limit) + .orElse(Integer.MAX_VALUE); + + return new FindTableOperation( + ctx, LogicalExpression.and(), new FindTableOperation.FindTableParams(limit)); + } + @Override public Operation resolveCollectionCommand( CommandContext ctx, FindCommand command) { diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolver.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolver.java index 7fc3809767..2249e74876 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolver.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolver.java @@ -10,9 +10,11 @@ import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonApiMetricsConfig; import io.stargate.sgv2.jsonapi.config.OperationsConfig; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; import io.stargate.sgv2.jsonapi.service.operation.model.Operation; import io.stargate.sgv2.jsonapi.service.operation.model.collections.CollectionReadType; import io.stargate.sgv2.jsonapi.service.operation.model.collections.FindOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.FindTableOperation; import io.stargate.sgv2.jsonapi.service.resolver.model.CommandResolver; import io.stargate.sgv2.jsonapi.service.resolver.model.impl.matcher.FilterableResolver; import io.stargate.sgv2.jsonapi.util.SortClauseUtil; @@ -51,6 +53,14 @@ public Class getCommandClass() { return FindOneCommand.class; } + @Override + public Operation resolveTableCommand( + CommandContext ctx, FindOneCommand command) { + + return new FindTableOperation( + ctx, LogicalExpression.and(), new FindTableOperation.FindTableParams(1)); + } + @Override public Operation resolveCollectionCommand( CommandContext ctx, FindOneCommand command) { diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertManyCommandResolver.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertManyCommandResolver.java index a7a24b40af..ff26de5f05 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertManyCommandResolver.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertManyCommandResolver.java @@ -4,12 +4,17 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.model.command.impl.InsertManyCommand; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; import io.stargate.sgv2.jsonapi.service.operation.model.Operation; +import io.stargate.sgv2.jsonapi.service.operation.model.collections.CollectionInsertAttempt; import io.stargate.sgv2.jsonapi.service.operation.model.collections.InsertOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.InsertTableOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.TableInsertAttempt; import io.stargate.sgv2.jsonapi.service.resolver.model.CommandResolver; import io.stargate.sgv2.jsonapi.service.shredding.Shredder; import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; import io.stargate.sgv2.jsonapi.service.shredding.model.WritableShreddedDocument; +import io.stargate.sgv2.jsonapi.service.shredding.tables.RowShredder; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.util.ArrayList; @@ -20,11 +25,13 @@ @ApplicationScoped public class InsertManyCommandResolver implements CommandResolver { - private final Shredder shredder; + private final Shredder documentShredder; + private final RowShredder rowShredder; @Inject - public InsertManyCommandResolver(Shredder shredder) { - this.shredder = shredder; + public InsertManyCommandResolver(Shredder documentShredder, RowShredder rowShredder) { + this.documentShredder = documentShredder; + this.rowShredder = rowShredder; } @Override @@ -41,22 +48,31 @@ public Operation resolveCollectionCommand( final List inputDocs = command.documents(); final int docCount = inputDocs.size(); - final List insertions = new ArrayList<>(docCount); + final List insertions = new ArrayList<>(docCount); for (int pos = 0; pos < docCount; ++pos) { - InsertOperation.InsertAttempt attempt; + CollectionInsertAttempt attempt; // Since exception thrown will prevent returning anything, need to instead pass a // reference for Shredder to populate with the document id as soon as it knows it // (there is at least one case fail occurs before it knows the id) AtomicReference idRef = new AtomicReference<>(); try { final WritableShreddedDocument shredded = - shredder.shred(ctx, inputDocs.get(pos), null, idRef); - attempt = InsertOperation.InsertAttempt.from(pos, shredded); + documentShredder.shred(ctx, inputDocs.get(pos), null, idRef); + attempt = CollectionInsertAttempt.from(pos, shredded); } catch (Exception e) { - attempt = new InsertOperation.InsertAttempt(pos, idRef.get(), e); + // TODO: need a base Shredding exception to catch + attempt = new CollectionInsertAttempt(pos, idRef.get(), e); } insertions.add(attempt); } return new InsertOperation(ctx, insertions, ordered, false, returnDocumentResponses); } + + @Override + public Operation resolveTableCommand( + CommandContext ctx, InsertManyCommand command) { + + return new InsertTableOperation( + ctx, TableInsertAttempt.create(rowShredder, command.documents())); + } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertOneCommandResolver.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertOneCommandResolver.java index 607046168e..0017e5c8ee 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertOneCommandResolver.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/InsertOneCommandResolver.java @@ -3,11 +3,15 @@ import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.model.command.impl.InsertOneCommand; import io.stargate.sgv2.jsonapi.service.cqldriver.executor.CollectionSchemaObject; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.TableSchemaObject; import io.stargate.sgv2.jsonapi.service.operation.model.Operation; import io.stargate.sgv2.jsonapi.service.operation.model.collections.InsertOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.InsertTableOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.tables.TableInsertAttempt; import io.stargate.sgv2.jsonapi.service.resolver.model.CommandResolver; import io.stargate.sgv2.jsonapi.service.shredding.Shredder; import io.stargate.sgv2.jsonapi.service.shredding.model.WritableShreddedDocument; +import io.stargate.sgv2.jsonapi.service.shredding.tables.RowShredder; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -15,11 +19,13 @@ @ApplicationScoped public class InsertOneCommandResolver implements CommandResolver { - private final Shredder shredder; + private final Shredder documentShredder; + private final RowShredder rowShredder; @Inject - public InsertOneCommandResolver(Shredder shredder) { - this.shredder = shredder; + public InsertOneCommandResolver(Shredder documentShredder, RowShredder rowShredder) { + this.documentShredder = documentShredder; + this.rowShredder = rowShredder; } @Override @@ -31,7 +37,7 @@ public Class getCommandClass() { public Operation resolveCollectionCommand( CommandContext ctx, InsertOneCommand command) { WritableShreddedDocument shreddedDocument = - shredder.shred( + documentShredder.shred( command.document(), null, ctx.schemaObject().indexingProjector(), @@ -40,4 +46,12 @@ public Operation resolveCollectionCommand( null); return InsertOperation.create(ctx, shreddedDocument); } + + @Override + public Operation resolveTableCommand( + CommandContext ctx, InsertOneCommand command) { + + return new InsertTableOperation( + ctx, TableInsertAttempt.create(rowShredder, command.document())); + } } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/DocRowIdentifer.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/DocRowIdentifer.java new file mode 100644 index 0000000000..48371b899c --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/DocRowIdentifer.java @@ -0,0 +1,11 @@ +package io.stargate.sgv2.jsonapi.service.shredding; + +import com.fasterxml.jackson.annotation.JsonValue; + +// TODO AARON base for antying to identify a doc or a row +public interface DocRowIdentifer { + + /** Method called by JSON serializer to get value to include in JSON output. */ + @JsonValue + Object value(); +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/WritableDocRow.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/WritableDocRow.java new file mode 100644 index 0000000000..ee6e62a590 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/WritableDocRow.java @@ -0,0 +1,6 @@ +package io.stargate.sgv2.jsonapi.service.shredding; + +public interface WritableDocRow { + + DocRowIdentifer docRowID(); +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/DocumentId.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/DocumentId.java index 8803c09caa..617dfbddc9 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/DocumentId.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/DocumentId.java @@ -1,6 +1,5 @@ package io.stargate.sgv2.jsonapi.service.shredding.model; -import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -9,6 +8,7 @@ import io.stargate.sgv2.jsonapi.config.constants.DocumentConstants; import io.stargate.sgv2.jsonapi.exception.ErrorCode; import io.stargate.sgv2.jsonapi.exception.JsonApiException; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; import io.stargate.sgv2.jsonapi.util.JsonUtil; import java.math.BigDecimal; import java.util.Date; @@ -28,12 +28,13 @@ * */ @RegisterForReflection -public interface DocumentId { +public interface DocumentId extends DocRowIdentifer { int typeId(); + // TODO AARON - moved to the base, remove comment when working /** Method called by JSON serializer to get value to include in JSON output. */ - @JsonValue - Object value(); + // @JsonValue + // Object value(); default JsonNode asJson(ObjectMapper mapper) { return asJson(mapper.getNodeFactory()); diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/WritableShreddedDocument.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/WritableShreddedDocument.java index 0b763e1330..c892feab1f 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/WritableShreddedDocument.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/model/WritableShreddedDocument.java @@ -6,8 +6,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.stargate.sgv2.jsonapi.exception.ErrorCode; import io.stargate.sgv2.jsonapi.exception.JsonApiException; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; import io.stargate.sgv2.jsonapi.service.shredding.JsonPath; import io.stargate.sgv2.jsonapi.service.shredding.ShredListener; +import io.stargate.sgv2.jsonapi.service.shredding.WritableDocRow; import io.stargate.sgv2.jsonapi.util.JsonUtil; import java.math.BigDecimal; import java.util.Arrays; @@ -44,10 +46,12 @@ public record WritableShreddedDocument( Map queryTimestampValues, Set queryNullValues, float[] queryVectorValues, - UUID nextTxID) { + UUID nextTxID) + implements WritableDocRow { @Override public boolean equals(Object o) { + // TODO: why do we have a customer equals and has for a record ? if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WritableShreddedDocument that = (WritableShreddedDocument) o; @@ -86,6 +90,11 @@ public int hashCode() { return result; } + @Override + public DocRowIdentifer docRowID() { + return id(); + } + public static Builder builder(DocumentId id, UUID txID, String docJson, JsonNode docJsonNode) { return new Builder(id, txID, docJson, docJsonNode); } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowId.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowId.java new file mode 100644 index 0000000000..90e9e3cbc9 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowId.java @@ -0,0 +1,12 @@ +package io.stargate.sgv2.jsonapi.service.shredding.tables; + +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; + +// TODO: AARON needs to hold all the values we identify as the parts of the PK for the row +public record RowId(Object[] values) implements DocRowIdentifer { + + @Override + public Object value() { + return values; + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowShredder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowShredder.java new file mode 100644 index 0000000000..005246ed63 --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/RowShredder.java @@ -0,0 +1,68 @@ +package io.stargate.sgv2.jsonapi.service.shredding.tables; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.stargate.sgv2.jsonapi.api.v1.metrics.JsonProcessingMetricsReporter; +import io.stargate.sgv2.jsonapi.config.DocumentLimitsConfig; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.util.HashMap; +import java.util.Map; + +/** AATON TODO shreds docs for rows */ +@ApplicationScoped +public class RowShredder { + + private final ObjectMapper objectMapper; + + private final DocumentLimitsConfig documentLimits; + + private final JsonProcessingMetricsReporter jsonProcessingMetricsReporter; + + @Inject + public RowShredder( + ObjectMapper objectMapper, + DocumentLimitsConfig documentLimits, + JsonProcessingMetricsReporter jsonProcessingMetricsReporter) { + this.objectMapper = objectMapper; + this.documentLimits = documentLimits; + this.jsonProcessingMetricsReporter = jsonProcessingMetricsReporter; + } + + /** + * Shreds the document to get it ready for the database, we need to know the table schema so we + * can work out the primary key and the columns to insert + * + * @param document + * @return + */ + public WriteableTableRow shred(JsonNode document) { + + // HACK for now we assume the primary is a field called primary key. + + Object keyObject; + try { + keyObject = objectMapper.treeToValue(document.get("key"), Object.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + Map columnValues = new HashMap<>(); + document + .fields() + .forEachRemaining( + entry -> { + // using fromCQL so it is case sensitive + try { + columnValues.put( + CqlIdentifier.fromCql(entry.getKey()), + objectMapper.treeToValue(entry.getValue(), Object.class)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }); + return new WriteableTableRow(new RowId(new Object[] {keyObject}), columnValues); + } +} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/WriteableTableRow.java b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/WriteableTableRow.java new file mode 100644 index 0000000000..b3de9f1c4b --- /dev/null +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/shredding/tables/WriteableTableRow.java @@ -0,0 +1,15 @@ +package io.stargate.sgv2.jsonapi.service.shredding.tables; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import io.stargate.sgv2.jsonapi.service.shredding.DocRowIdentifer; +import io.stargate.sgv2.jsonapi.service.shredding.WritableDocRow; +import java.util.Map; + +public record WriteableTableRow(RowId id, Map allColumnValues) + implements WritableDocRow { + + @Override + public DocRowIdentifer docRowID() { + return id(); + } +} diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index b4f5d4c2b6..d48448bd25 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -11,6 +11,11 @@ datastax-java-driver { } advanced.metadata { schema.request-timeout = 10 seconds + schema { + # Let's get metadata for "system" and "system_*" keyspaces + # (copied from default application.conf, removed first 2 entries) + refreshed-keyspaces = [ "!/^dse_.*/", "!solr_admin", "!OpsCenter" ] + } } advanced.metrics { id-generator{ @@ -62,4 +67,4 @@ datastax-java-driver { basic.request.timeout = 10 seconds } } -} \ No newline at end of file +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4a023fae37..c1fde37f39 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -48,6 +48,10 @@ stargate: multi-tenancy: enabled: false + # Is support for API Tables enabled? + tables: + enabled: false + quarkus: # general app properties application: diff --git a/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java b/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java index 4f5d30b6c2..31a5c4f62e 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/TestConstants.java @@ -24,6 +24,13 @@ public final class TestConstants { VectorConfig.notEnabledVectorConfig(), null); + public static final CollectionSchemaObject VECTOR_COLLECTION_SCHEMA_OBJECT = + new CollectionSchemaObject( + SCHEMA_OBJECT_NAME, + CollectionSchemaObject.IdConfig.defaultIdConfig(), + new VectorConfig(true, -1, CollectionSchemaObject.SimilarityFunction.COSINE, null), + null); + public static final KeyspaceSchemaObject KEYSPACE_SCHEMA_OBJECT = KeyspaceSchemaObject.fromSchemaObject(COLLECTION_SCHEMA_OBJECT); @@ -36,6 +43,9 @@ public final class TestConstants { public static final CommandContext COLLECTION_CONTEXT = new CommandContext<>(COLLECTION_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null); + public static final CommandContext VECTOR_COLLECTION_CONTEXT = + new CommandContext<>(VECTOR_COLLECTION_SCHEMA_OBJECT, null, null, null); + public static final CommandContext KEYSPACE_CONTEXT = new CommandContext<>(KEYSPACE_SCHEMA_OBJECT, null, TEST_COMMAND_NAME, null); diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java index 6ba54a8271..72b4c54dd1 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/cqldriver/executor/NamespaceCacheTest.java @@ -152,7 +152,7 @@ public void checkValidJsonApiTable() { new HashMap<>(), new HashMap<>()))); }); - NamespaceCache namespaceCache = new NamespaceCache("ks", queryExecutor, objectMapper); + NamespaceCache namespaceCache = createNamespaceCache(queryExecutor); var schemaObject = namespaceCache .getSchemaObject(dataApiRequestInfo, "table") @@ -283,7 +283,7 @@ public void checkValidJsonApiTableWithIndexing() { "{\"indexing\":{\"deny\":[\"comment\"]}}"), new HashMap<>()))); }); - NamespaceCache namespaceCache = new NamespaceCache("ks", queryExecutor, objectMapper); + NamespaceCache namespaceCache = createNamespaceCache(queryExecutor); var schemaObject = namespaceCache .getSchemaObject(dataApiRequestInfo, "table") @@ -349,7 +349,7 @@ public void checkInvalidJsonApiTable() { new HashMap<>(), new HashMap<>()))); }); - NamespaceCache namespaceCache = new NamespaceCache("ks", queryExecutor, objectMapper); + NamespaceCache namespaceCache = createNamespaceCache(queryExecutor); Throwable error = namespaceCache .getSchemaObject(dataApiRequestInfo, "table") @@ -368,4 +368,8 @@ public void checkInvalidJsonApiTable() { }); } } + + private NamespaceCache createNamespaceCache(QueryExecutor qe) { + return new NamespaceCache("ks", false, qe, objectMapper); + } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/ChainedComparatorTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/ChainedComparatorTest.java index e6b79a359a..3b6107d55d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/ChainedComparatorTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/ChainedComparatorTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; @@ -15,6 +16,7 @@ import java.util.Comparator; import java.util.List; import java.util.UUID; +import java.util.function.Supplier; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -26,6 +28,12 @@ public class ChainedComparatorTest { @Nested class Compare { + // Need a supplier for the ReadDocument, but it is never called + private static final Supplier throwingDocSupplier = + () -> { + throw new RuntimeException("Should not be called"); + }; + @Test public void compareBool() { Comparator comparator = @@ -36,12 +44,12 @@ public void compareBool() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(false))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))))) .isLessThan(0); @@ -51,12 +59,12 @@ public void compareBool() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(false))))) .isGreaterThan(0); // Same value ordered by document id - already ordered @@ -65,12 +73,12 @@ public void compareBool() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))))) .isLessThan(0); @@ -80,12 +88,12 @@ public void compareBool() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))))) .isGreaterThan(0); } @@ -100,12 +108,12 @@ public void compareText() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("xyz"))))) .isLessThan(0); @@ -115,12 +123,12 @@ public void compareText() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("xyz"))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))))) .isGreaterThan(0); // Same value ordered by document id - already ordered @@ -129,12 +137,12 @@ public void compareText() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))))) .isLessThan(0); @@ -144,12 +152,12 @@ public void compareText() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))))) .isGreaterThan(0); } @@ -164,12 +172,12 @@ public void compareNumber() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(2)))))) .isLessThan(0); @@ -179,12 +187,12 @@ public void compareNumber() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(2)))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))))) .isGreaterThan(0); // Same value ordered by document id - already ordered @@ -193,12 +201,12 @@ public void compareNumber() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))))) .isLessThan(0); @@ -208,12 +216,12 @@ public void compareNumber() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))))) .isGreaterThan(0); } @@ -228,12 +236,12 @@ public void compareDifferentTypes() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().booleanNode(true))), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().textNode("abc"))))) .isGreaterThan(0); @@ -243,12 +251,12 @@ public void compareDifferentTypes() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().nullNode())), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))))) .isLessThan(0); @@ -258,12 +266,12 @@ public void compareDifferentTypes() { ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().numberNode(new BigDecimal(1)))), ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().missingNode())))) .isGreaterThan(0); @@ -273,12 +281,12 @@ public void compareDifferentTypes() { ReadDocument.from( DocumentId.fromString("key2"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().nullNode())), ReadDocument.from( DocumentId.fromString("key1"), UUID.randomUUID(), - null, + throwingDocSupplier, List.of(objectMapper.getNodeFactory().missingNode())))) .isGreaterThan(0); } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/UpdateManyCommandResolverTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/UpdateManyCommandResolverTest.java index 1cd73c3684..f2f56fd7f1 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/UpdateManyCommandResolverTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/UpdateManyCommandResolverTest.java @@ -29,7 +29,6 @@ import io.stargate.sgv2.jsonapi.service.updater.DocumentUpdater; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; import jakarta.inject.Inject; -import java.util.Optional; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -248,7 +247,7 @@ public void dynamicFilterConditionSetVectorize() throws Exception { new DataVectorizer( TestEmbeddingProvider.commandContextWithVectorize.embeddingProvider(), objectMapper.getNodeFactory(), - Optional.empty(), + null, TestEmbeddingProvider.commandContextWithVectorize.schemaObject()) .vectorizeUpdateClause(updateClause); assertThat(operation)