Skip to content

Improve support for docdb - #374

Merged
adiom-mark merged 1 commit into
mainfrom
docdb
Mar 30, 2026
Merged

Improve support for docdb#374
adiom-mark merged 1 commit into
mainfrom
docdb

Conversation

@adiom-mark

@adiom-mark adiom-mark commented Mar 27, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Improvements
    • Connector now recognizes additional MongoDB flavors (including DocumentDB and CosmosDB VCORE) and adapts change-stream resume/start behavior per variant for better cross-platform compatibility.
  • New Features
    • Added robust document-ID sampling with type validation, deduplication and ordering to improve partitioning and sampling reliability across MongoDB variants, including special handling for DocumentDB.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 583bf0a7-9db5-4ad3-9341-3c13dcb0f6c7

📥 Commits

Reviewing files that changed from the base of the PR and between 2a93237 and e736e80.

📒 Files selected for processing (3)
  • connectors/mongo/conn.go
  • connectors/mongo/docdb.go
  • internal/app/options/connectorflags.go
✅ Files skipped from review due to trivial changes (1)
  • internal/app/options/connectorflags.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • connectors/mongo/docdb.go

📝 Walkthrough

Walkthrough

Adds DocumentDB-aware Mongo flavor handling, centralizes change-stream resume/start option selection, and introduces a DocumentDB-capable sampling implementation for partition planning that concurrently collects, deduplicates, validates, and type-sorts sampled _id values.

Changes

Cohort / File(s) Summary
MongoDB connection & change-streams
connectors/mongo/conn.go
Added flavor field on conn (initialized via GetMongoFlavor); introduced changeStreamOpts(cursor) to select resume/start semantics per flavor; updated StreamLSN and StreamUpdates to use the helper (preserves fullDocument=updateLookup for updates).
DocumentDB-aware sampling (new)
connectors/mongo/docdb.go
New file implementing (*conn) sampleIDs(ctx, col, numSamples). Non-DocumentDB: single $sample aggregation then sort. DocumentDB: concurrent single-sample aggregations, deduplication, uniform-type validation against a whitelist of supported _id BSON types, and type-aware sorting/comparison of bson.RawValue.
Connector flavor detection
internal/app/options/connectorflags.go
Extended Mongo connector URI flavor predicate to accept FlavorDocumentDB alongside existing Mongo flavors for mongodb:// and mongodb+srv:// URIs.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Conn as Connector (conn)
participant Mongo as MongoDB / DocumentDB
rect rgba(200,230,255,0.5)
Client->>Conn: Request partition plan (GeneratePlan)
Conn->>Mongo: sampleIDs aggregation(s)
Mongo-->>Conn: sampled _id values (single $sample or multiple single-samples for DocumentDB)
Conn->>Conn: dedupe, validate types, sort IDs, build partitions
Conn-->>Client: partition plan
end
rect rgba(200,255,200,0.5)
Client->>Conn: Start change-stream (StreamUpdates/StreamLSN) with cursor
Conn->>Conn: build options via changeStreamOpts(cursor) based on flavor
Conn->>Mongo: open change-stream with chosen resume/start option (SetResumeAfter or SetStartAfter) [+ fullDocument/updateLookup for updates]
Mongo-->>Conn: stream events
Conn-->>Client: streamed events
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hop through cursors, nibble each _id,
Fetching samples both far and wide,
DocumentDB gets a careful dance,
Resume or start—I'll pick by chance,
Partitions ordered, then off I glide.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Improve support for docdb' is vague and overly broad—it doesn't specify what improvements were made or which aspects of DocumentDB support were enhanced. Use a more specific title that describes the actual change, e.g., 'Add DocumentDB-aware sampling for collection partitioning' or 'Implement flavor-based change stream options for DocumentDB compatibility'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docdb

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@connectors/mongo/conn.go`:
- Around line 1008-1016: The changeStreamOpts function sets resume/start options
unconditionally which can pass an empty cursor; update changeStreamOpts (method
on type conn) to guard the resume logic by checking len(cursor) > 0 before
calling opts.SetResumeAfter(bson.Raw(cursor)) or
opts.SetStartAfter(bson.Raw(cursor)), mirroring the existing empty-cursor
pattern used in DecodeCursor; only call the Set* method when the cursor bytes
are non-empty so optional cursors are respected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f592fe42-3aba-470e-8092-606d772e32ad

📥 Commits

Reviewing files that changed from the base of the PR and between bb221b2 and 77a0efb.

📒 Files selected for processing (2)
  • connectors/mongo/conn.go
  • internal/app/options/connectorflags.go

Comment thread connectors/mongo/conn.go
Comment on lines +1008 to +1016
func (c *conn) changeStreamOpts(cursor []byte) *moptions.ChangeStreamOptions {
opts := moptions.ChangeStream()
if c.flavor == FlavorDocumentDB {
opts.SetResumeAfter(bson.Raw(cursor))
} else {
opts.SetStartAfter(bson.Raw(cursor))
}
return opts
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check helper implementation and whether cursor guards exist:"
rg -n -C3 'func \(c \*conn\) changeStreamOpts|SetResumeAfter|SetStartAfter|len\(cursor\)' connectors/mongo/conn.go

echo
echo "Check stream call sites using request cursors:"
rg -n -C2 'StreamLSN|StreamUpdates|GetCursor\(\)|changeStreamOpts\(' connectors/mongo/conn.go

echo
echo "Check API definitions/comments for cursor optionality:"
rg -n -C2 'message StreamLSNRequest|message StreamUpdatesRequest|cursor' --iglob '*.proto' || true

Repository: adiom-data/dsync

Length of output: 5660


Add empty cursor guard to changeStreamOpts for consistency.

The function currently passes cursor to SetResumeAfter/SetStartAfter without checking if it's empty. The codebase already uses this pattern in DecodeCursor (line 379), and proto definitions document cursor as optional ("if present"). Add the guard before setting resume options:

Proposed fix
 func (c *conn) changeStreamOpts(cursor []byte) *moptions.ChangeStreamOptions {
 	opts := moptions.ChangeStream()
+	if len(cursor) == 0 {
+		return opts
+	}
 	if c.flavor == FlavorDocumentDB {
 		opts.SetResumeAfter(bson.Raw(cursor))
 	} else {
 		opts.SetStartAfter(bson.Raw(cursor))
 	}
 	return opts
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *conn) changeStreamOpts(cursor []byte) *moptions.ChangeStreamOptions {
opts := moptions.ChangeStream()
if c.flavor == FlavorDocumentDB {
opts.SetResumeAfter(bson.Raw(cursor))
} else {
opts.SetStartAfter(bson.Raw(cursor))
}
return opts
}
func (c *conn) changeStreamOpts(cursor []byte) *moptions.ChangeStreamOptions {
opts := moptions.ChangeStream()
if len(cursor) == 0 {
return opts
}
if c.flavor == FlavorDocumentDB {
opts.SetResumeAfter(bson.Raw(cursor))
} else {
opts.SetStartAfter(bson.Raw(cursor))
}
return opts
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connectors/mongo/conn.go` around lines 1008 - 1016, The changeStreamOpts
function sets resume/start options unconditionally which can pass an empty
cursor; update changeStreamOpts (method on type conn) to guard the resume logic
by checking len(cursor) > 0 before calling opts.SetResumeAfter(bson.Raw(cursor))
or opts.SetStartAfter(bson.Raw(cursor)), mirroring the existing empty-cursor
pattern used in DecodeCursor; only call the Set* method when the cursor bytes
are non-empty so optional cursors are respected.

@alex-thc alex-thc changed the title Add support for docdb Improve support for docdb Mar 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
connectors/mongo/conn.go (1)

1008-1016: ⚠️ Potential issue | 🟡 Minor

Add empty cursor guard to changeStreamOpts.

When cursor is empty (initial stream with no resume point), calling SetResumeAfter or SetStartAfter with an empty bson.Raw may cause unexpected behavior. The codebase already guards empty cursors in DecodeCursor (line 379). Add an early return for consistency with the optional cursor semantics documented in proto definitions.

Proposed fix
 func (c *conn) changeStreamOpts(cursor []byte) *moptions.ChangeStreamOptions {
 	opts := moptions.ChangeStream()
+	if len(cursor) == 0 {
+		return opts
+	}
 	if c.flavor == FlavorDocumentDB {
 		opts.SetResumeAfter(bson.Raw(cursor))
 	} else {
 		opts.SetStartAfter(bson.Raw(cursor))
 	}
 	return opts
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connectors/mongo/conn.go` around lines 1008 - 1016, The changeStreamOpts
function should guard against an empty cursor byte slice and return default
moptions.ChangeStream() immediately; modify conn.changeStreamOpts(cursor []byte)
to check if len(cursor)==0 and return opts before calling opts.SetResumeAfter or
opts.SetStartAfter so empty cursors are treated as no-resume/start point
(consistent with DecodeCursor behavior), leaving the existing FlavorDocumentDB
branch and SetResumeAfter/SetStartAfter calls unchanged for non-empty cursors.
🧹 Nitpick comments (2)
connectors/mongo/docdb.go (2)

70-91: Consider limiting concurrency for large sample counts.

For large numSamples values (the code warns at >1,000,000 in conn.go), spawning one goroutine per sample could exhaust resources or overwhelm the database with concurrent requests. Consider using a semaphore or bounded worker pool.

Example bounded concurrency
+	const maxConcurrency = 100
 	results := make([]sampleResult, numSamples)
 	eg, ctx := errgroup.WithContext(ctx)
+	eg.SetLimit(maxConcurrency)
 	for i := int64(0); i < numSamples; i++ {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connectors/mongo/docdb.go` around lines 70 - 91, The current loop spawns one
goroutine per index which can exhaust resources when numSamples is large; modify
the logic that builds results (results, sampleResult, numSamples, errgroup,
col.Aggregate, eg.Go, eg.Wait) to use bounded concurrency by adding a semaphore
or worker-pool: create a configurable maxConcurrency (or derive one), acquire a
slot before launching each eg.Go and release it in the goroutine (or instead
push indices to a jobs channel and start maxConcurrency worker goroutines that
call col.Aggregate and write into results), keep using errgroup to collect
errors and call eg.Wait; this limits simultaneous Aggregate calls while
preserving the current result-writing and error-handling behavior.

27-45: Type mismatch in compareBSONRawValues will panic.

The function only switches on a.Type and assumes b has the same type. If called with mismatched types (e.g., due to a bug upstream), it will produce incorrect comparisons for the non-ObjectID/Binary cases or panic. The caller validates same-type at lines 108-111, so this is safe in current usage, but a defensive check would prevent future misuse.

Optional defensive guard
 func compareBSONRawValues(a, b bson.RawValue) int {
+	if a.Type != b.Type {
+		panic("compareBSONRawValues called with mismatched types")
+	}
 	switch a.Type {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connectors/mongo/docdb.go` around lines 27 - 45, compareBSONRawValues
currently assumes a.Type == b.Type and will misbehave or panic if types differ;
add a defensive guard at the start of compareBSONRawValues that checks if a.Type
!= b.Type and, when they differ, returns a deterministic ordering (for example
by returning cmp.Compare(int(a.Type), int(b.Type)) or similar) so callers get a
stable result instead of hitting incorrect logic or a panic; keep the existing
per-type switch for when types match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@connectors/mongo/docdb.go`:
- Around line 47-62: In sampleIDs, after the non-DocumentDB aggregation loop
that uses res.Next(ctx) and appends res.Current.Lookup("_id") to ids, check the
cursor error via res.Err() and return that error (wrapped with context) instead
of silently returning partial results; ensure this check happens before
returning ids and after res.Close(ctx) or immediately after the loop so any
iteration failures on the cursor are propagated from sampleIDs.

---

Duplicate comments:
In `@connectors/mongo/conn.go`:
- Around line 1008-1016: The changeStreamOpts function should guard against an
empty cursor byte slice and return default moptions.ChangeStream() immediately;
modify conn.changeStreamOpts(cursor []byte) to check if len(cursor)==0 and
return opts before calling opts.SetResumeAfter or opts.SetStartAfter so empty
cursors are treated as no-resume/start point (consistent with DecodeCursor
behavior), leaving the existing FlavorDocumentDB branch and
SetResumeAfter/SetStartAfter calls unchanged for non-empty cursors.

---

Nitpick comments:
In `@connectors/mongo/docdb.go`:
- Around line 70-91: The current loop spawns one goroutine per index which can
exhaust resources when numSamples is large; modify the logic that builds results
(results, sampleResult, numSamples, errgroup, col.Aggregate, eg.Go, eg.Wait) to
use bounded concurrency by adding a semaphore or worker-pool: create a
configurable maxConcurrency (or derive one), acquire a slot before launching
each eg.Go and release it in the goroutine (or instead push indices to a jobs
channel and start maxConcurrency worker goroutines that call col.Aggregate and
write into results), keep using errgroup to collect errors and call eg.Wait;
this limits simultaneous Aggregate calls while preserving the current
result-writing and error-handling behavior.
- Around line 27-45: compareBSONRawValues currently assumes a.Type == b.Type and
will misbehave or panic if types differ; add a defensive guard at the start of
compareBSONRawValues that checks if a.Type != b.Type and, when they differ,
returns a deterministic ordering (for example by returning
cmp.Compare(int(a.Type), int(b.Type)) or similar) so callers get a stable result
instead of hitting incorrect logic or a panic; keep the existing per-type switch
for when types match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 885513d1-c094-4899-914c-0c4d7f28cbfd

📥 Commits

Reviewing files that changed from the base of the PR and between 77a0efb and 2a93237.

📒 Files selected for processing (3)
  • connectors/mongo/conn.go
  • connectors/mongo/docdb.go
  • internal/app/options/connectorflags.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/app/options/connectorflags.go

Comment thread connectors/mongo/docdb.go
@adiom-mark
adiom-mark merged commit 65ec928 into main Mar 30, 2026
2 checks passed
@adiom-mark
adiom-mark deleted the docdb branch March 30, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant