feat: Add micronaut-data-nitrite embedded document store module - #3960
feat: Add micronaut-data-nitrite embedded document store module#3960sfkamath wants to merge 118 commits into
Conversation
|
@radovanradic thanks for your previous GitHub workflow approvals this past week. This is now ready for release, pending CI checks. The latest round of work focused mainly on getting the Nitrite module into much better Jakarta Data TCK shape. The most important part is the Jakarta TCK compliance work: enabling the common TCK paths, adding coverage for static metamodel sorting, ignore-case queries, id function projection queries, Jakarta expression predicates, and the remaining restricted-query paths. The last set of commits also includes fixes for DTO projection context, projection aliases, expression field reference normalization, sorting and pagination behavior, cursored pages, unique single-result enforcement, and some cleanup around query operator handling. NullAway is now satisfied, the public Nitrite API has the missing Javadocs, and the docs have been refreshed to clarify the supported query boundaries. Assuming CI stays green, I think this branch is in a good state for release |
Is there any way to constrain the Copilot review to 'data-nitrite/src/main/java' |
|
@copilot review only data-nitrite/src/main/java |
…tyPathImpl and fix date conversion tests
…ture - Replace NitriteEntityMapper with PropertyStrategy-based serialization using Serde for boundary conversion and entity metadata caching - Replace NitriteOperationsFactory to use dedicated JacksonMapperModule and inject Serde ObjectMapper for entity↔Map conversion - Add helper classes: ValueConverter, ObjectRepositoryMapper, ObjectRepositoryWriter, CollectionWriter, NitriteOperationsHelper, NitriteOperationContext - Adapt DefaultNitriteRepositoryOperations, stored/prepared query wrappers, and NitriteStoredQuery interface to new signatures
- Add NitriteQueryExecutor and NitriteCriteriaExecutor for prepared/criteria query execution with join fetching - Add CollectionFieldMapper, CollectionProjectionMapper, CollectionAggregator for native projections and aggregations - Extend NitriteQueryParser with parseSelectClause, extractProjectionField, and hasProjection for SELECT/$project support - Fix NitriteCriteriaExecutor to use public PersistentEntityCriteriaQuery interfaces instead of removed internal class
- Replace BaseQueryDefinition parameter with PersistentEntity in constructor - Update Criteria-based buildSelect/buildUpdate/buildDelete calls to pass query.persistentEntity() or definition.persistentEntity()
- Implement SyncCascadeOperationsHelper and NitriteOperationsHelper - Add NitriteEntityOperations, NitriteEntitiesOperations, and cascadeOperations field - Replace persist/persistAll/update/updateAll/delete/deleteAll bodies with delegation to new entity operation classes - Add placeholder implementations for NitriteOperationsHelper methods (toFilterValue, updateEntityId, logging) – will be finalised in next commit
- Delegate toFilterValue to NitriteEntityMapper for proper type coercion - Use ConversionService in updateEntityId for correct ID assignment - Enable DEBUG-level logging for insert/update/find operations
Import full test suite covering: - Cascade operations and lifecycle events - Transaction management - Upsert operations with version tracking - Associations (one-to-many, many-to-many) - Criteria queries and MongoDB-like syntax - Embedded entities and embedded IDs - Projections and aggregations - Snake case conversions - Custom storage configurations Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit brings over the remaining unported logic from the 4.14.x branch: DefaultNitriteRepositoryOperations.java (+1330 lines): - Port remaining insertOne, updateOne, deleteOne logic with proper OperationType handling and cascade operations - Port persistEntity and computePersistOperations methods for nested entity persistence - Port executeEntityOperation and entity relationship handling (NestedJoinAlias, etc.) - Port prepareResult and extractId logic for proper entity transformation - Port collectAssociations and cascade save operations with CascadeOp - Port optimistic locking support with LoadedEntity tracking - Port txContext and inTransaction logic for transaction-aware operations NitriteCriteriaExecutor.java (new +408 lines): - Port criteria executor implementation for criteria-based query execution - Handle association joins and nested property paths in criteria - Port predicate building with NitritePredicateVisitor - Port ResolveContext handling for correlated subqueries Supporting changes: - NitriteEntityMapper: Add safeGetIdentity() helper to handle RuntimePersistentEntity.getIdentity() throwing IllegalStateException for embedded entities (5.0.x behavior vs 4.14.x returning null) - NitriteEntitiesOperations: Use updateOptions(true) for upsert semantics - NitriteUpdateExecutor: Added with proper update operation handling - NitriteTransactionManager: Add doSuspend()/doResume() overrides - NitriteCriteriaSpec: Add integration tests for criteria and association joins
- Restore updateOne() to OperationType.UPDATE + op.update() so cascade updates go through the UPDATE branch and call persistNewCascadeChildren, fixing new cascade children not being persisted on re-save - Use updateOptions(upsert = versionProp == null) in single-entity UPDATE path: versioned entities use strict update (version filter must fail for OL), non-versioned entities use upsert to support pre-assigned IDs - Fix NitriteEntitiesOperations batch UPDATE to use updateOptions(false): updateAll() must never insert - Guard associatedEntity.getIdentity() in persistNewCascadeChildren with try-catch ISE — skip entities with no identity (e.g. NitriteComplexValue) rather than throwing - Batch UPDATE path uses upsert = versionProp == null — non-versioned entities get insert-if-absent, versioned get strict update - Add LOG.debug to ObjectRepositoryWriter ISE catch to aid diagnosis Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…omposite ID path mapping
- Map embedded @EmbeddedId associations to _id in NitriteDB document paths, traversing
nested identity associations inline rather than stopping at FK field names
- Use $between operator for range queries instead of decomposing to $gte/$lte
- Optimize $not:{$in:[...]} to $nin:[...]
- Implement visitArrayContains using $all operator
- Fix sort query guard to detect unquoted $sort key
- Deduplicate conjunction/disjunction visit logic into visitLogical
- Handle non-IPredicate property path in single-predicate conjunction (treat as isTrue)
- Use pipeline format when both match and sort are present in buildSelect
- Extract pipeline $match stage via extractFilterMap in criteria executor and operations
- Update NitriteCriteriaSpec expectations to match corrected output formats
- NitriteFilterBuilder: recognize _id as identity prefix in dot-path lookup
- parseSortFromJsonQuery: handle pipeline list format
- NitriteCriteriaExecutor.buildFindOptions: scan all pipeline stages for $sort, $skip, $limit (not just the first matching stage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cent and update spec Fix integer promotion bug where ternary forced Integer to Double; update NitriteQueryParserSpec to reflect correct escape-sequence unescaping behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d break DynamicFieldNode cycle Extract PatternConverter, ValueResolver, SpatialFilterFactory, and AssociationFilterResolver from the god-class NitriteFilterBuilder. Replace the operator switch with an OperatorHandler registry. Eliminate the DynamicFieldNode→buildFieldFilter back-reference by introducing AssociationFieldNode with an AssociationFieldEvaluator callback and extracting the shared buildAssociationOrNestedField path. Add PatternConverterSpec and ValueResolverSpec unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…odule Simplify conditional expressions, remove redundant code, and apply style improvements suggested by IntelliJ across all Nitrite runtime classes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and array-contains filter - NitriteQueryBuilder: generate $lookup/$unwind stages from join paths (ported from MongoQueryBuilder) - NitriteQueryExecutor: non-joined associations stay null; joined associations explicitly set to empty list when no children found - NitriteCategory: productList default changed to null so absent fields are never initialised by the mapper - NitritePredicateVisitor: fix MANY_TO_ONE field path to use join alias; skip empty collections in toJsonString - NitritePredicateVisitor: add $all filter handler using resolveCollection + per-element eq checks (AND-ed; single-element skips Filter.and wrapper) - NitriteCriteriaSpec: update join test expected strings to Nitrite entity/field naming conventions Tests (NitriteDocumentRepositorySpec): 6 new tests covering findByIdIn/NotIn, findByNameIn (List/array), findByNameLike/NotLike, array-contains (String/List + criteria), Map<String, Owner> round-trip, and criteria IN variants Tests (NitriteMultiOneToManySpec): 1 new test asserting non-joined collection is null Support files: NitriteDocument, NitriteDocumentOwner, NitriteDocumentEntityRepository, NitriteCriteriaPersonRepository Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… extract shared utilities - Add NitriteTypeRegistry: single registration point (TypeEntry<T> with strategy, write, fromNumber, fromString) for all concrete temporal and string-serialised types; supertype walk handles abstract keys (Charset, ZoneId subclasses) - Add NitriteTypeRegistrySpec: 37 Spock tests covering write/read round-trips, supertype resolution, no-op read cases, strategyFor and hasEntry - Extract PropertyStrategy to its own file; classifyValueStrategy 8-case type list collapsed to single NitriteTypeRegistry.hasEntry check - ValueConverter.toFilterValueStatic: 10 explicit cases → default registry write - ValueConverter.convertWithTemporalHandling: Number/String if-chains → registry read - NitriteEntityMapper.convertToDocumentInternal: per-type strategy arms grouped into single NitriteTypeRegistry.write call - NitriteEntityMapper.convertFromDocumentValue: explicit temporal type list replaced with NitriteTypeRegistry.hasEntry guard - ValueResolver.preConvertForFilter: 4-case switch → NitriteTypeRegistry.write - Extract NameUtils (camelToSnake/snakeToCamel); remove duplicates from NitriteEntityMapper, DefaultNitriteRepositoryOperations, CollectionAggregator, AssociationFilterResolver - Extract NitriteEntityMeta and WritablePropertyMeta records to own files - Delete CollectionWriter (confirmed dead code) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…triteQueryBinder Deduplicates JSON parameter resolution logic from DNRO and NQE into a single internal utility class, eliminating copy-paste across the two query execution paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dropped in 5.0.x) Nitrite has no native SQL support. The SQL parsing layer was ported from 4.14.x where it served Query2-style queries; those were removed in 5.0.x, leaving ~400 lines of dead code. Deletes NitriteUpdateExecutor, removes isSql() from the stored/prepared query chain, drops all parseWhereClause/ parseFilter*/parseSortFromSqlQuery/reorderParamsForSql methods from DNRO, NQE, NitriteOperationsHelper, and NitriteQueryParser. Test SQL @query annotations replaced with JSON equivalents. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mantics, and mapper correctness Review-requested: - Support named datasources under micronaut.nitrite.<name>, with per-configuration repository operations, an implicit default in-memory datasource, and @NitriteRepository bound to a named datasource; database bean resolution is now qualified per datasource, fixing "Multiple possible bean candidates found: [Nitrite, Nitrite]" once a second datasource was configured. - Detect optimistic locking from entity version metadata/query bindings instead of method-name text; reject updates with a null identity instead of upserting a transient entity. - Execute max/min/sum/avg criteria selections instead of treating any numeric result as a count; resolve criteria update fields to persisted names with distinct parameter indexes per bound assignment. - Fail startup when the RocksDB adapter or a present-but-uninitializable spatial module is missing, instead of silently falling back or logging a warning. Bugs surfaced fixing the above: - Nitrite has no arithmetic update operator: increments/multiplies are read-modify-write per document, and were unsynchronized — concurrent updates lost writes (8 threads x 50 increments landed on 150/400). Now serialized per collection via CollectionUpdateLock; overflow raises DataAccessException instead of a raw ArithmeticException. - Dotted property paths only resolved their last segment, so nested @MappedProperty paths addressed a field absent from the stored document — broke criteria updates and nested sort ordering alike. - Read-only (constructor-only) properties were dropped from writable metadata, so immutable entities (Kotlin non-null constructor params) persisted nothing but their identity; required constructor associations deferred to null and broke constructors that reject null. - SQL-shaped generated queries mis-split literal AND inside quoted strings and silently dropped unbound assignments; batch delete/deleteAll reported wrong counts. - IntelliJ inspection pass: dangling javadoc, identity lookup throwing instead of falling back to persisted-name matching, identical catch branches, unguarded nullable QueryResult, raw parameterized-type usage. Docs corrected to match: config prefix was documented as nitrite.* instead of micronaut.nitrite.<name>.*, multi-datasource usage undocumented, association fetching/transaction/limitations text updated to match actual behavior. Co-Authored-By: OpenAI Codex Sol 5.6 <noreply@openai.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5ee4157 to
e15df75
Compare
|
Checkstyle failed |
| * @param database the Nitrite database | ||
| */ | ||
| public NitriteConnectionOperations(Nitrite database) { | ||
| this.database = database; |
There was a problem hiding this comment.
The database is now created per datasource, but transaction operations and NitriteTransactionHolder remain global and inject the primary Nitrite instance. A secondary repository can therefore use the wrong transaction/database.
| Document update = repositoryWriter.toDocument(entity); | ||
| if (update != null) { | ||
| helper.logUpdate(collection.getName(), filter, update); | ||
| boolean upsert = meta.versionProp() == null; |
There was a problem hiding this comment.
update() and updateAll() insert entities that are not already present when no @Version property exists (upsert = true in that case). If this is intentional Nitrite-specific behavior, please document it clearly and add coverage, including the distinction from save() and handling of missing IDs.
| } | ||
|
|
||
| private void appendIdentitySort(Map<String, Sort.Order> orders, RuntimePersistentEntity<?> entity) { | ||
| if (!entity.hasIdentity() || entity.hasCompositeIdentity()) { |
There was a problem hiding this comment.
Composite IDs are not used as a secondary sort key. As a result, cursor pagination sorted by a non-unique field can skip records that share the same sort value.
| // Try to get values - field might be stored as camelCase or snake_case | ||
| List<Object> values = docs.stream() | ||
| .map(d -> { | ||
| Object val = d.get(fieldName); |
There was a problem hiding this comment.
Looks like that derived aggregates only try the Java property name and snake-case form. A property mapped to a custom name such as total_value will not be found.
| return NONE; | ||
| } | ||
|
|
||
| String idField = entity.getIdentity().getPersistedName(); |
There was a problem hiding this comment.
It seems it still uses getPersistedName(), while identities are stored under canonical id. Nested reverse-association queries with a mapped ID can return no results.
Also, entity.getIdentity() is not checked with hasIdentity()
Adds
micronaut-data-nitrite, an embedded document store module for Micronaut Data, filling the gap that SQLite fills for relational use cases. It targets embedded apps, CLI tools, desktop apps, and integration tests where running MongoDB is too heavy.New Module:
data-nitritedata-mongodbconventions, includingQueryBuilder,AbstractConnectionOperations, and document processor integration.QueryModelAPI is present for compatibility but is not the primary implementation path.@JoinacrossONE_TO_MANY,MANY_TO_MANY, andMANY_TO_ONE.Non-Nitrite Changes
data-runtime/RuntimePersistentPropertyPathImpl: adds implicit association join support. Navigating an association property in a Criteria query now resolves through a join rather than failing, enablingMANY_TO_ONEand association-path traversal across all backends.data-model/AbstractExpressionandInPredicate: enablesin(...)predicates on computed Criteria expressions and supports builder-less expression trees. This lets backend implementations receive and process Jakarta Data expression predicates instead of failing while constructing the shared Criteria tree.