Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project are documented in this file.

## [Unreleased]

### Changed
- Default local project version moved to `0.1.10-SNAPSHOT`.

## [0.1.10] - 2026-07-21

### Added
- Added MongoDB-style `$min`/`$max` update operators with BSON ordering, mixed numeric types, dates, dotted paths, upserts, and modifier conflict detection.
- Added expression-based aggregation-pipeline updates for `$addFields`/`$set`, `$project`/`$unset`, and `$replaceRoot`/`$replaceWith`.
- Added aggregation expressions for conditional/comparison/type/null handling, min/max, basic arithmetic, `$mergeObjects`, and UTC `$dateTrunc` with calendar units and `binSize`.
- Expanded `$group` with core, set/object merge, percentile/median, and first/last/min/max/top/bottom N accumulators.
- Added `$setWindowFields` partition/sort support with `$shift`, `$documentNumber`, `$rank`, and `$denseRank`.

## [0.1.9] - 2026-05-09

### Added
- Added `$unwind.includeArrayIndex` support, including MongoDB-compatible `null` index handling for scalar and preserved null/missing/empty inputs.
- Added `listCollections`, `drop`, and `dropDatabase` command handlers for metadata lookup and fixture cleanup flows.
Expand Down Expand Up @@ -54,7 +68,7 @@ All notable changes to this project are documented in this file.
- Fixed upsert seed extraction to honor equality clauses nested inside `$and`, restoring duplicate-key behavior for `findOneAndUpdate` lock-style filters.

### Changed
- Default local project version moved to `0.1.9-SNAPSHOT`.
- Default local project version moved to `0.1.9-SNAPSHOT` before the `v0.1.9` release.

## [0.1.3] - 2026-02-24

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ This project targets integration-test compatibility for common Spring data paths
| --- | --- | --- |
| Command surface | 25 handlers | Mix of `Supported` and `Partial` |
| Query language | Core comparison/logical/array/regex + partial `$expr` (including `$add` subset) | Advanced parity incomplete |
| Aggregation | Core stages, `$unwind.includeArrayIndex`, selected Tier-2 stages + minimal `$graphLookup` subset | Full operator coverage not implemented |
| Aggregation | Core stages, analytics accumulators, UTC `$dateTrunc`, `$setWindowFields` sequence subset, and minimal `$graphLookup` | Full operator coverage not implemented |
| Transactions | Single-process session/transaction flow | Namespace-aware commit merge + snapshot reads (`find`/`aggregate`/`countDocuments`) + deterministic retry labels/contracts |
| Deployment profile | Standalone + single-node replica-set semantic profile | Replica-set profile exposes primary-only handshake/URI semantics for driver compatibility |
| Wire protocol | `OP_MSG` + `OP_QUERY` | In-process ingress and standalone TCP launcher mode, with OP_QUERY namespace-based `$db` fallback |
Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = providers.gradleProperty("publishGroup").orElse("io.github.midagedev").get()
version = providers.gradleProperty("publishVersion").orElse("0.1.9-SNAPSHOT").get()
version = providers.gradleProperty("publishVersion").orElse("0.1.10-SNAPSHOT").get()

repositories {
mavenCentral()
Expand Down Expand Up @@ -42,6 +42,7 @@ dependencies {

testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("org.mongodb:mongodb-driver-sync:4.11.2")
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
testImplementation("org.springframework:spring-context:6.1.17")
Expand Down
26 changes: 15 additions & 11 deletions docs/COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Compatibility Matrix

Status date: 2026-05-09
Status date: 2026-07-21

This page describes implemented behavior in this repository. It is a code-level matrix, not a MongoDB claim.

Expand Down Expand Up @@ -28,15 +28,15 @@ Certification context:
| `listCollections` | Partial | Cursor-shaped collection metadata subset for fixture discovery |
| `drop` | Partial | Collection cleanup subset with deterministic `NamespaceNotFound` error |
| `dropDatabase` | Partial | Database-scoped cleanup subset |
| `update` | Partial | Operator set intentionally limited; update pipeline subset supports `$set`/`$unset` stages without expression evaluation |
| `update` | Partial | Supports `$min`/`$max` and core modifiers; aggregation-pipeline updates support MongoDB's update-stage allowlist with the expression subset below |
| `delete` | Supported | `limit` 0/1 behavior |
| `bulkWrite` | Partial | Ordered mode only (`ordered=true`); supports `insertOne/updateOne/updateMany/deleteOne/deleteMany/replaceOne` and stops on first write error |
| `clientBulkWrite` | Partial | UTF importer subset rewrites ordered single-namespace models to `bulkWrite`; mixed namespaces, `ordered=false`, and `verboseResults=true` are deterministic unsupported paths |
| `count` | Partial | Alias path routed through `countDocuments` semantics in the test-backend profile |
| `countDocuments` | Partial | Filter + skip/limit + hint/readConcern; collation subset applied to filter comparison |
| `runCommand` | Partial | UTF importer subset supports `ping`, `buildInfo`, `listIndexes`, `listCollections`, `count`, `distinct`, `drop`, `dropDatabase`; other command names fail with deterministic unsupported reasons |
| `replaceOne` | Partial | Rewrites to single replacement `update` path (`multi=false`) |
| `findOneAndUpdate` | Partial | Rewrites to `findAndModify`; supports operator updates plus update-pipeline subset (`$set`/`$unset`, no expression evaluation), `arrayFilters` subset, and projection include/exclude subset (including `_id` override) |
| `findOneAndUpdate` | Partial | Rewrites to `findAndModify`; supports operator and expression-based pipeline updates, `arrayFilters` subset, and projection include/exclude subset (including `_id` override) |
| `findOneAndReplace` | Partial | Rewrites to `findAndModify`; replacement updates only; supports projection include/exclude subset (including `_id` override) |
| `commitTransaction`, `abortTransaction` | Supported | Session/txn envelope supported |

Expand Down Expand Up @@ -67,7 +67,7 @@ Not implemented:
Implemented stages:
- `$match`
- `$project`
- `$group` (subset: `$sum`, `$first`, `$addToSet`)
- `$group` with `$sum`, `$avg`, `$min`, `$max`, `$first`, `$last`, `$push`, `$addToSet`, `$mergeObjects`, `$percentile`, `$median`, `$firstN`, `$lastN`, `$minN`, `$maxN`, `$topN`, and `$bottomN`
- `$sort`
- `$limit`, `$skip`
- `$unwind` (including `includeArrayIndex`)
Expand All @@ -82,23 +82,27 @@ Implemented stages:
- `$lookup` (local/foreign and pipeline+let subset)
- `$unionWith`
- `$graphLookup` (minimal subset: `from`, `startWith`, `connectFromField`, `connectToField`, `as`, optional `maxDepth`)
- `$setWindowFields` subset with `partitionBy`, `sortBy`, `$shift`, `$documentNumber`, `$rank`, and `$denseRank`
- `$out` (terminal string-target subset: replaces target collection contents and returns empty result set)
- `$merge` (terminal string-target or `{into: <collection>}` subset; merges by `_id`)

Not implemented or partial:
- unsupported stages return deterministic fail-fast
- many advanced expression operators are still missing
- `$group` accumulators other than `$sum` are not available
- expression support includes field references, `$literal`, `$cond`, `$ifNull`, `$type`, comparisons, logical operators, `$min`/`$max`, basic arithmetic, `$mergeObjects`, and `$dateTrunc`
- `$dateTrunc` supports second through year units, `binSize`, `startOfWeek`, and UTC timezone aliases; non-UTC timezones fail explicitly
- `$percentile`/`$median` accept `method: "approximate"` and use deterministic exact interpolation internally
- window accumulators and range/time windows beyond the listed `$setWindowFields` subset are not implemented
- `$merge` options beyond the terminal string / `{into: <collection>}` subset are deterministic unsupported paths
- `$graphLookup` options outside current subset (for example `depthField`, `restrictSearchWithMatch`) are deterministic unsupported paths
- `bypassDocumentValidation` for aggregate is excluded from current differential corpus

## Update Semantics

Supported:
- operator updates: `$set`, `$inc`, `$unset`
- operator updates: `$set`, `$setOnInsert`, `$inc`, `$min`, `$max`, `$unset`, `$addToSet`
- `arrayFilters` subset for `$set`/`$unset` paths using `$[identifier]` bindings
- update pipeline subset: `$set`/`$unset` stages with literal values
- update pipeline stages: `$addFields`/`$set`, `$project`/`$unset`, and `$replaceRoot`/`$replaceWith`
- update pipeline expressions: field references, `$literal`, `$cond`, `$ifNull`, `$type`, comparisons, logical operators, `$min`/`$max`, basic arithmetic, `$mergeObjects`, and `$dateTrunc`
- replacement updates (with `multi=false`)
- upsert for operator and replacement forms
- same update constraints apply to `bulkWrite` update/replace operations
Expand All @@ -107,8 +111,8 @@ Not supported:
- advanced `arrayFilters` forms (unsupported operators, missing bindings, unsupported path/operator combinations)
- positional updates (`$`, `$[]`)
- update operators outside the supported set
- update pipeline stages outside `$set`/`$unset`
- update pipeline expressions (field references/operator expressions inside stage values)
- update pipeline stages outside MongoDB's update-stage allowlist
- aggregation expressions outside the listed subset
- replacement updates with `multi=true`

## R3 Query/Update Corpus Exclusions
Expand All @@ -122,7 +126,7 @@ differential parity counts:
- dot/dollar insert payload forms outside the deterministic insert subset; dollar-prefixed subfields under `_id` fail with `code=52`
- `runCommand` command names outside the imported subset (`ping`, `buildInfo`, `listIndexes`, `listCollections`, `count`, `distinct`, `drop`, `dropDatabase`)
- update operations using unsupported `arrayFilters` forms (outside `$set`/`$unset` subset)
- update pipeline forms outside the supported subset (`$set`/`$unset` stages with literal values)
- update pipeline forms outside the supported stage/expression subsets listed above
- replacement updates requested with `multi=true`

## UTF Import Profiles
Expand Down
7 changes: 4 additions & 3 deletions docs/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release Checklist

Status date: 2026-05-09
Status date: 2026-07-21

## R3 Certification Sign-Off

Expand All @@ -25,10 +25,10 @@ Status date: 2026-05-09

## Current Release-Line Notes

- Latest Java release tag: `v0.1.8` (run `23785415137`, commit `7b8ef1f`).
- Latest Java release tag: `v0.1.9` (run `25603328584`, commit `46ddb9b`).
- Latest Node adapter tag: `node-v0.1.4` (run `22379340586`, commit `868bbc7`).
- Latest compatibility certification snapshot: commit `b44be73` (runs `25603109902`, `25603109886`, `25603109905`, `25603109900`).
- Next Java tag candidate: `v0.1.9`; regenerate certification artifacts against the release-candidate commit, not reuse historical tag evidence.
- Next Java tag candidate: `v0.1.10`; regenerate certification artifacts against the release-candidate commit, not reuse historical tag evidence.

## Tagging Gate

Expand Down Expand Up @@ -77,6 +77,7 @@ Use this when validating `@jongodb/memory-server` canary automation:

| Version | Date (UTC) | Commit | Maven | GitHub Actions |
| --- | --- | --- | --- | --- |
| `0.1.9` | `2026-05-09` | `46ddb9b` | `io.github.midagedev:jongodb:0.1.9` | run `25603328584` |
| `0.1.8` | `2026-03-31` | `7b8ef1f` | `io.github.midagedev:jongodb:0.1.8` | run `23785415137` |
| `0.1.7` | `2026-03-05` | `b7c4a13` | `io.github.midagedev:jongodb:0.1.7` | run `22723048147` |
| `0.1.6` | `2026-03-01` | `12a259c` | `io.github.midagedev:jongodb:0.1.6` | run `22541258962` |
Expand Down
3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Completed in current wave:
- `listCollections`, `drop`, and `dropDatabase` command subsets added for fixture discovery/cleanup
- tier-0 TTL runtime pruning added for single-field non-partial TTL indexes
- deterministic R3 ledger mismatches reduced for `_id` dollar subfields, `updateMany` replacement validation, and transactional `createIndexes`
- analytics-oriented update/aggregation parity expanded with `$min`/`$max`, expression-based pipeline updates, `$dateTrunc`, percentile/median and N accumulators, and a deterministic `$setWindowFields` subset

## Current Focus

Expand All @@ -69,7 +70,7 @@ Primary measures:
- close remaining aggregate-stage unsupported surface beyond the current `$merge` terminal subset
- expand collation semantics beyond current subset (`locale`/`strength`/`caseLevel`)
- expand TTL runtime behavior beyond the current single-field lazy-prune subset
- expand update/operator coverage beyond current `arrayFilters` subset (advanced positional/pipeline expressions)
- expand update/operator coverage beyond current `arrayFilters` and pipeline-expression subsets (advanced positional updates and remaining expressions)
- expand supported transaction operations in unified suites while preserving deterministic behavior

## Out of Scope (Current Phase)
Expand Down
8 changes: 4 additions & 4 deletions docs/SUPPORT_MATRIX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Support Matrix

Status date: 2026-05-09
Status date: 2026-07-21

This matrix is a versioned support boundary for integration-test usage.
Source artifact: `build/reports/r2-compatibility/r2-support-manifest.json`.
Expand All @@ -20,9 +20,9 @@ Source artifact: `build/reports/r2-compatibility/r2-support-manifest.json`.
| `query.eq-ne-compare` | query | Supported | Core comparison operators |
| `query.elemMatch-all-regex` | query | Supported | Array and regex operators |
| `query.expr-subset` | query | Partial | Subset: eq/ne/gt/gte/lt/lte/and/or/not/literal/add |
| `aggregation.match-project-group` | aggregation | Supported | Tier-1 pipeline stages, including `$unwind.includeArrayIndex` |
| `aggregation.lookup-union-facet` | aggregation | Partial | Tier-2 subset without full expression parity (includes minimal `$graphLookup` option subset) |
| `aggregation.expression-operators` | aggregation | Partial | Limited expression coverage |
| `aggregation.match-project-group` | aggregation | Supported | Tier-1 pipeline stages, analytics accumulators, and `$unwind.includeArrayIndex` |
| `aggregation.lookup-union-facet` | aggregation | Partial | Tier-2 subset without full expression parity (includes minimal `$graphLookup` and `$setWindowFields` option subsets) |
| `aggregation.expression-operators` | aggregation | Partial | Conditional/comparison/type/null, min/max, arithmetic, mergeObjects, and UTC dateTrunc subsets |
| `index.unique-sparse-partial` | index | Supported | Unique/sparse/partial |
| `index.collation-metadata` | index | Supported | Collation metadata round-trip |
| `index.collation-semantic` | index | Partial | Subset: locale/strength/caseLevel on query-sort-distinct and unique index checks |
Expand Down
1 change: 1 addition & 0 deletions jongodb-spring-suite/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {

testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test>().configureEach {
Expand Down
1 change: 1 addition & 0 deletions jongodb-testkit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {

testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test>().configureEach {
Expand Down
35 changes: 33 additions & 2 deletions src/main/java/org/jongodb/command/CommandStore.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jongodb.command;

import java.util.List;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.jongodb.engine.CollationSupport;
Expand Down Expand Up @@ -146,13 +147,43 @@ public IndexMetadata(
}

record UpdateRequest(
BsonDocument query, BsonDocument update, boolean multi, boolean upsert, List<BsonDocument> arrayFilters) {
BsonDocument query,
BsonDocument update,
BsonArray updatePipeline,
boolean multi,
boolean upsert,
List<BsonDocument> arrayFilters) {
public UpdateRequest(final BsonDocument query, final BsonDocument update, final boolean multi, final boolean upsert) {
this(query, update, multi, upsert, List.of());
this(query, update, null, multi, upsert, List.of());
}

public UpdateRequest(
final BsonDocument query,
final BsonDocument update,
final boolean multi,
final boolean upsert,
final List<BsonDocument> arrayFilters) {
this(query, update, null, multi, upsert, arrayFilters);
}

public UpdateRequest(
final BsonDocument query,
final BsonArray updatePipeline,
final boolean multi,
final boolean upsert) {
this(query, null, updatePipeline, multi, upsert, List.of());
}

public UpdateRequest {
if ((update == null) == (updatePipeline == null)) {
throw new IllegalArgumentException("exactly one of update or updatePipeline must be specified");
}
update = update == null ? null : update.clone();
updatePipeline = updatePipeline == null ? null : updatePipeline.clone();
arrayFilters = copyArrayFilters(arrayFilters);
if (updatePipeline != null && !arrayFilters.isEmpty()) {
throw new IllegalArgumentException("arrayFilters is not allowed with pipeline updates");
}
}

private static List<BsonDocument> copyArrayFilters(final List<BsonDocument> source) {
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/org/jongodb/command/EngineBackedCommandStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,24 @@ public UpdateResult update(final String database, final String collection, final
for (int index = 0; index < updates.size(); index++) {
final UpdateRequest updateRequest = updates.get(index);
final Document query = toDocument(Objects.requireNonNull(updateRequest.query(), "query"));
final Document update = toDocument(Objects.requireNonNull(updateRequest.update(), "update"));
final List<Document> arrayFilters = new ArrayList<>(updateRequest.arrayFilters().size());
for (final BsonDocument arrayFilter : updateRequest.arrayFilters()) {
arrayFilters.add(toDocument(Objects.requireNonNull(arrayFilter, "arrayFilters entries must not be null")));
}

final UpdateManyResult result = collectionStore.update(
query, update, updateRequest.multi(), updateRequest.upsert(), List.copyOf(arrayFilters));
final UpdateManyResult result;
if (updateRequest.updatePipeline() != null) {
final List<Document> pipeline = new ArrayList<>(updateRequest.updatePipeline().size());
for (final BsonValue stageValue : updateRequest.updatePipeline()) {
pipeline.add(toDocument(stageValue.asDocument()));
}
result = collectionStore.updatePipeline(
query, List.copyOf(pipeline), updateRequest.multi(), updateRequest.upsert());
} else {
final Document update = toDocument(Objects.requireNonNull(updateRequest.update(), "update"));
result = collectionStore.update(
query, update, updateRequest.multi(), updateRequest.upsert(), List.copyOf(arrayFilters));
}
matchedCount += toBoundedInt(result.matchedCount());
modifiedCount += toBoundedInt(result.modifiedCount());
if (result.upserted()) {
Expand Down
Loading
Loading