Compare commits

...
126 Commits
Author SHA1 Message Date
NotAlexNoyle c645ed1adf Exclude CODEOWNERS from language statistics 2026-11-01 09:18:32 -05:00
NotAlexNoyle 87234d9f76 Add CODEOWNERS (one staff approval) 2026-11-01 09:18:32 -05:00
Alexey Palazhchenko 0d5eb3343a Prepare v2.7.0 release (#5538) 2025-11-10 21:18:24 +04:00
Alexey Palazhchenko 52ad11f0af Bump deps (#5537) 2025-11-10 20:45:21 +04:00
Alexey Palazhchenko 71b6eb3a08 Bump DocumentDB (#5536) 2025-11-09 22:46:54 +04:00
5cb62a1155 Add MCP tools (#5452)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-11-09 20:36:10 +04:00
Alexey Palazhchenko 867537dd01 Use Go 1.25.4 (#5534) 2025-11-08 09:35:59 +04:00
487880fd19 Add basic MCP server (#5279)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-11-06 09:08:44 +04:00
Chi FujiiandAlexey Palazhchenko 3b29cece0c Refactor DataAPI with graceful shutdown and add OpenAPI specification endpoint (#5357)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-10-27 18:55:12 +04:00
Alexey Palazhchenko c1bb0b6db6 Update DocumentDB (#5528) 2025-10-22 18:46:58 +04:00
Alexey Palazhchenko a04b20a58f Use forked lazyerrors (#5527) 2025-10-22 16:13:02 +04:00
Josh Soref 2b2ce550d4 Fix CODEOWNERS grammar (#5495)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-10-21 12:51:38 +04:00
Alexey Palazhchenko 87293d3434 Skip flaky test for now (#5526) 2025-10-21 12:50:19 +04:00
Alexey Palazhchenko 7b0d200262 Use Go 1.25.3 (#5525) 2025-10-21 11:43:35 +04:00
Patryk KwiatekandAlexey Palazhchenko d1d037a030 Add a test for *documentdb.Conn finalizing (#5484)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-10-21 11:28:03 +04:00
Alexey Palazhchenko c37a62b0a2 Bump dependencies (#5519) 2025-10-09 17:57:31 +04:00
Alexey Palazhchenko cff1577d19 Improve proxy mode observability (#5440) 2025-10-03 19:53:25 +04:00
Alexey Palazhchenko efa50d4877 Add tracing of SQL queries (#5514) 2025-10-02 21:02:46 +04:00
Alexey Palazhchenko 067eb2f143 Rename OpenTelemetry spans (#5512) 2025-09-29 14:58:13 +04:00
Alexey Palazhchenko 4e8e51751d Minor observability cleanup (#5511) 2025-09-28 22:32:36 +03:00
Alexey Palazhchenko ae0b94de12 Bump deps (#5509) 2025-09-28 20:56:00 +03:00
Alexey Palazhchenko 2c24483c06 Use Go 1.25.1 (#5508) 2025-09-28 15:48:00 +03:00
Josh Soref 221d5cbd08 Fix spelling in .go files (#5492)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-09-08 10:33:12 +09:00
Alexey Palazhchenko 62ce1019b1 Simplify licensing 2025-09-07 12:08:29 +04:00
Josh Soref ef9934e3ed Replace malicious container registry references with ghcr (#5486) 2025-09-03 20:05:21 +04:00
Alexey Palazhchenko e78eaffb58 Finalize PostgreSQL metrics (#5485) 2025-09-03 15:25:44 +04:00
Chi FujiiandAlexey Palazhchenko 370b1586c5 Add PostgreSQL metrics (#5427)
Closes #5402.

Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-09-02 14:50:16 +04:00
Alexander Tobi FashakinandCopilot 1f8d160296 Update cloud installation docs (#5466)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-01 19:52:16 +04:00
3656ebd1c4 Use generated structs in Data API (#5382)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-09-01 13:24:21 +04:00
Patryk KwiatekandAlexey Palazhchenko e3a7406b9e Update wire dependency (#5465)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-09-01 12:48:10 +04:00
Alexey Palazhchenko 4847e59e25 Update dependencies (#5479) 2025-09-01 11:55:26 +04:00
Alexey Palazhchenko b6fa806add Move to documentdb/documentdb (#5468) 2025-08-27 12:35:57 +04:00
Chi FujiiandAlexey Palazhchenko 5394f00550 Avoid hijacking connections from the pool and immediately closing them (#5443)
Closes #5402.
Closes FerretDB/FerretDB-DocumentDB#270.

Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-08-27 11:37:26 +04:00
Alexey Palazhchenko 9a48b7132c Add helpers for lazy logging (#5467) 2025-08-27 10:26:35 +04:00
Alexey Palazhchenko 4cea93524d Fix FerretDB Cloud link 🤦 2025-08-26 19:38:39 +04:00
Alexey Palazhchenko 3cd4554d87 Add Cease and Desist Notice (#5462) 2025-08-25 18:50:08 +04:00
Alexey Palazhchenko 2e6ca25078 Add FerretDB Cloud blog post (#5461) 2025-08-25 17:04:31 +04:00
Alexey Palazhchenko 901a8f6030 Add MongoDB's complaint (#5460) 2025-08-25 12:45:03 +04:00
Alexey Palazhchenko b1feb6377d Add documentation page stub for MCP server (#5451) 2025-08-22 07:49:36 +00:00
Alexey Palazhchenko 3fd8c982a5 Bump deps (#5455) 2025-08-22 11:32:52 +04:00
Alexander Tobi FashakinandCopilot c05de8bdea Document cloud instance deployment and setup (#5424)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 20:15:06 +04:00
Alexey Palazhchenko d97f2612b8 Add TODO comment about Debian 13 Trixie (#5450)
Refs FerretDB/FerretDB#5449.
2025-08-21 13:42:59 +04:00
Alexey Palazhchenko 6d8e86b774 Remove flaky test (#5444) 2025-08-20 12:17:35 +04:00
Alexander Tobi Fashakin 7a117e768d Update compatible apps (#5442) 2025-08-20 05:51:21 +00:00
Alexander Tobi FashakinandCopilot bfd1650ee2 Update content guide (#5441)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 19:09:52 +04:00
Alexey Palazhchenko d9a14c883e Add comments to supervisord config (#5430) 2025-08-19 14:07:17 +04:00
Alexey Palazhchenko 2b652dae20 Use middleware (#5377) 2025-08-19 13:57:06 +04:00
Alexey Palazhchenko 6631594402 Add middleware (#5437) 2025-08-19 07:38:31 +04:00
c5f69fe962 Add blog post on Formio (#5395)
Co-authored-by: Chi Fujii <chi.fujii@ferretdb.io>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-08-18 22:39:46 +04:00
Alexey Palazhchenko a65042d381 Update expected PostgreSQL version (#5438) 2025-08-18 20:12:33 +04:00
Alexey Palazhchenko 24981ad991 Tweak conninfo (#5435) 2025-08-18 12:23:23 +04:00
Alexey Palazhchenko 2078f859f3 Make Run wait for all Handle calls to finish (#5434) 2025-08-18 11:52:42 +04:00
Chi FujiiandAlexey Palazhchenko 16670ff34f Upload YugabyteDB log files (#5401)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-08-18 09:27:56 +04:00
Alexey Palazhchenko b994d18792 Update DocumentDB (#5431) 2025-08-14 19:21:04 +04:00
Alexey Palazhchenko 4ea90be4aa Use QEMU on arm64 for Yugabyte for now (#5429) 2025-08-14 12:32:49 +04:00
3770c1e4eb Add release blog post for FerretDB v2.5.0 (#5412)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-08-14 12:06:53 +04:00
Chi Fujii 5fd75c160a Bump YugabyteDB to stable version (#5428) 2025-08-14 10:55:16 +04:00
Alexey Palazhchenko c60131a69c Update documentation to point to the next release (#5423) 2025-08-14 10:54:45 +04:00
Alexey Palazhchenko b4eda04f37 Prepare v2.5.0 release (#5414) 2025-08-12 15:06:25 +04:00
Alexey Palazhchenko 6e2d3affd9 Update DocumentDB (#5411) 2025-08-12 10:25:34 +04:00
Alexey Palazhchenko 2cbf8cac53 Update Prometheus metrics (#5403) 2025-08-11 19:51:18 +04:00
Patryk Kwiatek b933b1f365 Test flaky tests detection (#5393) 2025-08-06 14:12:10 +04:00
Alexey Palazhchenko 96d34a4615 Enable MongoDB test commands (#5400) 2025-08-04 11:17:26 +04:00
Alexey Palazhchenko 1a5a6ec3c3 Update DocumentDB (#5398) 2025-08-01 18:29:12 +04:00
Chi Fujii 997c02f379 Run all YugabyteDB integration tests (#5283) 2025-08-01 14:31:33 +04:00
Alexey Palazhchenko 2412bf3595 Make a resource.Untrack thread-safe (#5396) 2025-08-01 12:10:01 +04:00
Alexey Palazhchenko 20697b6b74 Small tweaks (#5394) 2025-07-31 09:53:28 +04:00
Alexander Tobi FashakinandAlexey Palazhchenko cb18c69dcc Add WeKan blog post (#5381)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-30 19:32:21 +04:00
Alexey Palazhchenko e99a07db05 Bump DocumentDB version (#5392) 2025-07-30 19:13:25 +04:00
Chi FujiiandAlexey Palazhchenko b1189f2594 Add YugabyteDB integration tests (#5274)
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-30 16:53:31 +04:00
Alexey Palazhchenko 783d39086c Encapsulate PostgreSQL pool in the Handler (#5389) 2025-07-30 11:28:47 +04:00
Alexey Palazhchenko c87f9e3c18 Small cleanup (#5388) 2025-07-30 11:02:43 +04:00
Patryk Kwiatek e5615b35ca Use Codecov for flaky integration tests detection (#5361) 2025-07-30 05:51:39 +00:00
Alexey PalazhchenkoandCopilot 327a8fb759 Extract wiring into a separate package (#5383)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-30 09:23:06 +04:00
Alexander Tobi Fashakin 319128c43f Enable full visibility for images on zoom (#5346) 2025-07-28 20:21:27 +04:00
Alexey Palazhchenko bccd6276dd Update DocumentDB (#5380) 2025-07-28 12:17:48 +04:00
Alexey Palazhchenko 1b5913f2bf Simplify flags handling (#5378) 2025-07-28 09:16:48 +04:00
Chi Fujii 66f9bc149d Make accessing YugabyteDB logs easier (#5362)
Closes #85.
2025-07-25 08:12:33 +00:00
Chi Fujii f06bf851a9 Skip YugabyteDB setup for arm64 (#5372)
Closes #85.
2025-07-25 07:06:06 +00:00
Alexey Palazhchenko c891abbdf6 Refactor normal and error responses (#5326) 2025-07-24 19:24:29 +04:00
Alexey PalazhchenkoandChi Fujii f65c7b643a Tweak Ollama and MCPHost setup (#5375)
Co-authored-by: Chi Fujii <chi.fujii@ferretdb.io>
2025-07-24 09:06:14 +04:00
Alexander Tobi Fashakin bc0e9128e3 Add Vault blog post (#5368)
Closes #5152.
2025-07-23 19:44:59 +04:00
Patryk Kwiatek e1ea96acf4 Add components in codecov (#5364) 2025-07-23 11:06:56 +04:00
Alexey Palazhchenko f64490cf94 Update DocumentDB (#5353) 2025-07-17 12:14:08 +04:00
Alexander Tobi Fashakin 5a05644fed Add release blogpost for 2.4.0 (#5354) 2025-07-16 19:56:04 +04:00
Alexey Palazhchenko 9a45f6bc26 Prepare v2.4.0 release (#5344) 2025-07-16 11:31:22 +04:00
d9d3a9f8dd Add HyperDX blog post (#5334)
Closes #5226.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Chi Fujii <chi.fujii@ferretdb.io>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-15 20:19:07 +04:00
Alexey Palazhchenko 91feb6e54a Update DocumentDB (#5342) 2025-07-15 17:13:59 +04:00
Alexander Tobi Fashakin fc1ec1ad61 Add compatible applications (#5335) 2025-07-10 08:06:09 +00:00
Chi Fujii b1afd1f15a Add YugabyteDB to local setup (#5258)
Closes #85.
2025-07-09 09:44:12 +00:00
Alexey PalazhchenkoandCopilot 746c84a0d6 Split command handling and handlers (#5328)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-09 11:05:44 +04:00
2936407d35 Add DBeaver compatibility blogpost (#5321)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Chi Fujii <chi.fujii@ferretdb.io>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-08 20:06:47 +04:00
Alexey Palazhchenko 07449f2391 Avoid extra marshaling (#5327) 2025-07-08 19:28:47 +04:00
Chi FujiiandAlexey Palazhchenko c62a0ec6ba Allow users to perform user management commands (#5314)
Closes #85.

Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-08 09:11:38 +04:00
Alexander Tobi Fashakin 9ae0a666e9 Add GrowthBook to compatible apps (#5319) 2025-07-04 08:10:04 +00:00
Chi Fujii 13b36b44ef Update TODO URL for dropUsers test (#5324) 2025-07-04 11:59:07 +04:00
Alexey Palazhchenko 1916b89173 Remove must.NotFail(wire[bson].NewXXX( pattern (#5318) 2025-07-04 08:49:53 +09:00
d558f38ee8 Add Heyform compatibility blog post (#5316)
Co-authored-by: Chi Fujii <chi.fujii@ferretdb.io>
Co-authored-by: Alexey Palazhchenko <alexey.palazhchenko@ferretdb.io>
2025-07-03 18:39:38 +04:00
Alexey Palazhchenko 7eacf8cefc Refactor responses handling (#5310) 2025-07-03 14:57:18 +04:00
Chi Fujii d6245127ac Update TODO URLs (#5317) 2025-07-02 09:43:56 +04:00
Alexey Palazhchenko f23e436f39 Do not use deprecated method (#5312) 2025-07-01 19:57:09 +04:00
Alexey Palazhchenko a630f68a9f Refactor requests handling (#5292) 2025-06-30 13:22:54 +04:00
Alexey Palazhchenko 7f7072c83a Bump deps (#5307) 2025-06-29 16:25:31 +02:00
Alexander Tobi Fashakin f248bb1356 Add Growthbook blogpost (#5304) 2025-06-27 17:57:33 +02:00
Alexey Palazhchenko b4bd17b678 Minor refactoring of Data API (#5302) 2025-06-27 11:23:57 +02:00
Alexey PalazhchenkoandCopilot 2f974ee36e Extend middleware requests and responses (#5301)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-27 08:47:14 +02:00
Chi Fujii cec3175583 Cleanup NodeBB blog post (#5294) 2025-06-26 10:53:19 +00:00
Alexey Palazhchenko 969cf59ef3 Add Dependabot configuration for mcphost (#5296) 2025-06-26 11:02:38 +02:00
Chi Fujii 169e5d3800 Bring back Codecov and Coveralls integration (#5276) 2025-06-26 09:57:18 +02:00
Chi Fujii 1618cf7b19 Add mcphost as tool (#5287) 2025-06-26 09:56:31 +02:00
Alexander Tobi Fashakin 0d84f2c324 Add NodeBB to compatible apps (#5293) 2025-06-25 16:52:11 +02:00
Alexander Tobi Fashakin 871f60d169 Add NodeBB compatibility blog post (#5285) 2025-06-25 14:39:00 +02:00
Alexander Tobi Fashakin e66693f38b Add Librechat to compatibility section (#5286) 2025-06-25 09:30:36 +02:00
Alexander Tobi Fashakin 30bdf62714 Add blog post on setting up LibreChat with FerretDB (#4999) 2025-06-23 16:42:37 +02:00
Alexander Tobi Fashakin c4b9de7f45 Add PayloadCMS to compatible apps (#5270) 2025-06-23 12:22:54 +00:00
Alexey Palazhchenko 211b1348a5 Update DocumentDB (#5282) 2025-06-20 16:38:45 +04:00
Alexey Palazhchenko eb59f61d73 Wrap long lines in a blog post (#5280) 2025-06-20 13:44:29 +04:00
Alexander Tobi Fashakin d539b13641 Add Compass to compatible apps (#5275) 2025-06-19 12:29:17 +04:00
Alexander Tobi Fashakin 117d335fa3 Add blog post on using Compass GUI (#5263) 2025-06-18 19:43:51 +04:00
Alexey Palazhchenko 0dc0844eb3 Use debug-level log messages for continuations (#5266)
Closes #5265.
2025-06-17 13:35:06 +00:00
Alexey Palazhchenko c58bcdb8e8 Remove skipping of bypassEmptyTsReplacement (#5267) 2025-06-17 11:19:38 +00:00
Alexander Tobi Fashakin c765a2fb06 Add blog post for Payload CMS (#5257) 2025-06-17 10:20:03 +04:00
Alexey Palazhchenko 89eb76043c Update DocumentDB (#5268) 2025-06-16 17:35:10 +04:00
Alexander Tobi Fashakin 84f3b9cf7c Fix social links (#5264) 2025-06-16 07:05:48 +00:00
Alexey Palazhchenko 51335dc57f Update DocumentDB (#5261) 2025-06-16 10:21:56 +04:00
Alexey Palazhchenko 0ad2ad90fa Revert "Remove hack for bypassEmptyTsReplacement (#5217)" (#5256) 2025-06-12 17:03:53 +04:00
578 changed files with 15633 additions and 5220 deletions

No files matched your search

+3
View File
@@ -4,4 +4,7 @@
*.webp filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.go diff=golang
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+1 -1
View File
@@ -1,5 +1,5 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Order is important; the last matching pattern takes the most precedence.
# Order is important; the last matching pattern has the highest precedence.
* @AlekSi @chilagrow
+5 -4
View File
@@ -6,9 +6,10 @@
2. Update DocumentDB image tags to point to release in:
- [eval image](../build/ferretdb/eval.Dockerfile);
- [eval-dev image](../build/ferretdb/eval-dev.Dockerfile);
- [local image](../build/deps/postgres-documentdb.Dockerfile).
- [local image](../build/deps/postgres-documentdb.Dockerfile);
- [bw image](../build/ferretdb-bw/Dockerfile).
3. Update constants in [`documentdb.go`](../build/version/documentdb.go).
4. Check DocumentDB images tags in documentation (where we specify full tags).
4. Check DocumentDB images/tags in documentation (where we specify full tags).
5. Re-run tests, linters.
6. Check issues and pull requests, update milestones and labels.
7. Check if there are breaking changes or anything that should be reflected in the changelog and/or user documentation.
@@ -63,8 +64,8 @@
1. Publish and announce blog post.
2. Add link to blog post to `CHANGELOG.md` and release page.
3. Bump the latest version on https://beacon.ferretdb.com and https://beacon.ferretdb.io.
4. Update DocumentDB image tags to point back to development version.
5. Update DocumentDB images tags in the current documentation (where we specify full tags)
4. Update DocumentDB images/tags in Dockerfiles to point back to development version.
5. Update DocumentDB images/tags in the current documentation (where we specify full tags)
to point to the next (non-existing) version.
6. Update URLs with `/refs/tags/` in them in the current documentation
to point to the next (non-existing) version.
+26 -1
View File
@@ -30,7 +30,32 @@ flag_management:
# https://docs.codecov.com/docs/pull-request-comments
comment:
layout: reach,diff,files,flags
layout: reach,diff,files,flags,components
# https://docs.codecov.com/docs/components
component_management:
default_rules:
statuses:
- type: project
target: auto
threshold: 1%
individual_components:
- component_id: clientconn
name: "clientconn"
paths:
- internal/clientconn/**
- component_id: dataapi
name: "dataapi"
paths:
- internal/dataapi/**
- component_id: documentdb
name: "documentdb"
paths:
- internal/documentdb/**
- component_id: handler
name: "handler"
paths:
- internal/handler/**
# https://docs.codecov.com/docs/github-checks
# Annotations may be hidden by pressing the "a" key or by unselecting "Show annotations" in the top right of the file.
+14
View File
@@ -188,6 +188,20 @@ updates:
day: "wednesday"
time: "01:42"
# no mcphost in main-v1
- package-ecosystem: "gomod"
directory: "/tools/mcphost"
labels: ["deps", "not ready"]
assignees: [AlekSi]
open-pull-requests-limit: 20
groups:
mcphost:
patterns: ["*"]
schedule:
interval: "weekly"
day: "wednesday"
time: "01:42"
# no submodules in main-v1
- package-ecosystem: "gitsubmodule"
directory: "/"
+49 -10
View File
@@ -12,10 +12,6 @@ on:
shard_total:
required: true
type: number
coveralls:
required: false
type: boolean
default: false
test_timeout:
required: false
type: string
@@ -24,6 +20,10 @@ on:
required: false
type: boolean
default: false
skip_flaky_check:
required: false
type: boolean
default: false
env:
GOPATH: /home/runner/go
@@ -43,7 +43,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -77,6 +77,14 @@ jobs:
- name: Run init
run: bin/task init
- name: Setup Ollama and MCPHost
run: |
mkdir -p tmp/logs
curl -fsSL https://ollama.com/install.sh | sh
sudo systemctl stop ollama
bin/task ollama > tmp/logs/ollama.log 2>&1 &
bin/task mcphost > tmp/logs/mcphost.log 2>&1 &
- name: Wait for and setup environment
run: bin/task env-setup
@@ -95,22 +103,53 @@ jobs:
- name: Collect logs
if: failure()
run: |
mkdir -p /tmp/logs
bin/task env-logs-collect > /tmp/logs/compose-logs.txt
cp -v integration/*.json.tmp integration/*.txt.tmp /tmp/logs/
bin/task env-logs-collect
cp -v integration/*.json.tmp integration/*.txt.tmp tmp/logs/
- name: Compress logs before upload
if: failure()
run: zip -r -q -9 logs.zip /tmp/logs
run: zip -r -q -9 logs.zip tmp/logs
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: logs-${{ inputs.task }}-${{ inputs.shard_index }}-${{ inputs.shard_total }}
path: logs.zip
retention-days: 3
- name: Upload coverage information to codecov
if: always()
uses: codecov/codecov-action@v5
with:
files: ./integration/integration-${{ inputs.task }}.txt
flags: integration,${{ inputs.task }},${{ inputs.task }}-${{ inputs.shard_index }}
disable_file_fixes: true
fail_ci_if_error: true
verbose: true
- name: Upload coverage information to coveralls
if: always()
uses: coverallsapp/github-action@v2
with:
file: ./integration/integration-${{ inputs.task }}.txt
flag-name: integration-${{ inputs.task }}-${{ inputs.shard_index }}
parallel: true
- name: Convert test results to JUnit XML format
if: always() && !inputs.skip_flaky_check
run: >
./bin/go-junit-report -parser=gojson
-in ./integration/*.json.tmp
-out ./integration/${{ inputs.task }}.xml
- name: Upload test results to Codecov
if: always()
uses: codecov/test-results-action@v1
with:
flags: integration,${{ inputs.task }},${{ inputs.task }}-${{ inputs.shard_index }}
file: ./integration/${{ inputs.task }}.xml
# we don't want them on CI
- name: Clean test and fuzz caches
if: always()
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
+2 -2
View File
@@ -43,7 +43,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: true
@@ -90,7 +90,7 @@ jobs:
# it has to be exactly "docs.tar" for actions/deploy-pages below
- name: Upload archive artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: docs
path: tmp/docs.tar
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
+59 -8
View File
@@ -42,7 +42,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -102,7 +102,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -123,6 +123,16 @@ jobs:
run: go generate -x
working-directory: tools
# install.sh starts ollama service, stop it since we want to run with `bin/task ollama` with environment variables
- name: Setup Ollama
run: |
curl -fsSL https://ollama.com/install.sh | sudo -E sh
sudo systemctl stop ollama
- name: Start Ollama in the background
run: |
bin/task ollama &
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -133,6 +143,10 @@ jobs:
- name: Start environment
run: bin/task env-up-detach
- name: Pull and load Ollama model
run: |
bin/task ollama-model
- name: Run init
run: bin/task init
@@ -142,6 +156,7 @@ jobs:
- name: Regenerate and reformat
run: bin/task gen
env:
# must be a SUPERUSER to ensure parameter_default is set
DOCUMENTDB_GEN_URL: postgres://username:password@127.0.0.1:5432/postgres
- name: Build bin/ferretdb
@@ -153,6 +168,38 @@ jobs:
GOFLAGS: ${{ runner.debug == '1' && '-v' || '' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for tools
- name: Upload coverage information to codecov
if: always()
uses: codecov/codecov-action@v5
with:
files: ./cover.txt
flags: unit
disable_file_fixes: true
fail_ci_if_error: true
verbose: true
- name: Upload coverage information to coveralls
if: always()
uses: coverallsapp/github-action@v2
with:
file: ./cover.txt
flag-name: unit
parallel: true
- name: Convert test results to JUnit XML format
if: always()
run: >
./bin/go-junit-report
-in ./unit.txt
-out ./unit.xml
- name: Upload test results to Codecov
if: always()
uses: codecov/test-results-action@v1
with:
flags: unit
file: ./unit.xml
# we don't want them on CI
- name: Clean test and fuzz caches
if: always()
@@ -180,18 +227,19 @@ jobs:
strategy:
fail-fast: false
matrix:
task: [postgresql]
task: [postgresql, yugabytedb]
shard_index: [1, 2, 3, 4, 5]
shard_total: [5]
include:
- { task: "mongodb", shard_index: 1, shard_total: 1 }
- { task: "yugabytedb", skip_flaky_check: true } # Too many failures for now
uses: ./.github/workflows/_integration.yml
with:
task: ${{ matrix.task }}
shard_index: ${{ matrix.shard_index }}
shard_total: ${{ matrix.shard_total }}
coveralls: true
skip_flaky_check: ${{ matrix.skip_flaky_check || false }}
integration-no-xfail:
name: postgresql no-xfail
@@ -223,8 +271,11 @@ jobs:
if: always() && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready'))
steps:
- name: Do nothing
run: echo 'nothing'
- name: Submit coveralls
if: always()
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
env-data:
name: env-data
@@ -244,7 +295,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -304,7 +355,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `golangci-lint run --new` to work
lfs: false # LFS is used only by website
+6 -6
View File
@@ -64,7 +64,7 @@ jobs:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -72,7 +72,7 @@ jobs:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout pull request code
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -156,7 +156,7 @@ jobs:
run: bin/task build-development
- name: Upload development binaries
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: bin-dev
path: tmp/bin-dev/
@@ -167,7 +167,7 @@ jobs:
run: bin/task build-production
- name: Upload production binaries
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: bin
path: tmp/bin/
@@ -178,7 +178,7 @@ jobs:
run: bin/task packages
- name: Upload .deb packages
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: debs
path: tmp/debs/
@@ -186,7 +186,7 @@ jobs:
if-no-files-found: error
- name: Upload .rpm packages
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: rpms
path: tmp/rpms/
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for `git describe` to work
lfs: false # LFS is used only by website
@@ -106,7 +106,7 @@ jobs:
- name: Upload FerretDB log
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: ferretdb-log
path: ferretdb-log.zip
+2 -2
View File
@@ -45,7 +45,7 @@ jobs:
steps:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for wrangler to work
lfs: true
@@ -116,7 +116,7 @@ jobs:
# TODO https://github.com/FerretDB/github-actions/issues/211
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # for wrangler to work
lfs: true
+1
View File
@@ -13,6 +13,7 @@ telemetry.json
*.tmp
*.txt
!website/static/*.txt
*.xml
.task/
bin/
+2 -1
View File
@@ -53,6 +53,7 @@ linters-settings:
- "!**/internal/documentdb/**.go"
- "!**/internal/driver/*.go"
- "!**/internal/handler/**.go"
- "!**/internal/mcp/*.go"
- "!**/internal/mongoerrors/*.go"
- "!**/internal/util/logging/*.go"
- "!**/internal/util/scram/*.go"
@@ -179,7 +180,7 @@ linters-settings:
alias: otelsdkresource
- pkg: go.opentelemetry.io/otel/sdk/trace
alias: otelsdktrace
- pkg: go.opentelemetry.io/otel/semconv/v1.21.0
- pkg: go.opentelemetry.io/otel/semconv/v1.34.0
alias: otelsemconv
# ineffassign
lll:
+220
View File
@@ -2,6 +2,226 @@
<!-- markdownlint-disable MD024 MD034 -->
## [v2.7.0](https://github.com/FerretDB/FerretDB/releases/tag/v2.7.0) (2025-11-10)
(We skipped v2.6.0 to align DocumentDB and FerretDB version numbers.)
This version works best with
[DocumentDB v0.107.0-ferretdb-2.7.0](https://github.com/FerretDB/documentdb/releases/tag/v0.107.0-ferretdb-2.7.0).
### Breaking changes
There are some changes in our Prometheus metrics and OpenTelemetry traces.
Please note that they are
[not stable yet](https://docs.ferretdb.io/v2.7/configuration/observability/).
### What's Changed
#### Basic MCP server
This release adds basic MCP server support.
Future release will bring additional functionality.
#### Connectivity improvements
PostgreSQL connections are now reused, often yielding modest performance gains.
### New Features 🎉
- Add basic MCP server by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5279
- Implement DataAPI graceful shutdown and add OpenAPI spec endpoint by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5357
- Improve proxy mode observability by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5440
- Add tracing of SQL queries by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5514
### Enhancements 🛠
- Add `PostgreSQL` metrics by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5427
- Rename OpenTelemetry spans by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5512
### Documentation 📄
- Add blog post on Formio by @Fashander in https://github.com/FerretDB/FerretDB/pull/5395
- Document cloud instance deployment and setup by @Fashander in https://github.com/FerretDB/FerretDB/pull/5424
- Update content guide by @Fashander in https://github.com/FerretDB/FerretDB/pull/5441
- Update compatible apps by @Fashander in https://github.com/FerretDB/FerretDB/pull/5442
- Add documentation page stub for MCP server by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5451
- Add FerretDB Cloud blog post by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5461
- Update cloud installation docs by @Fashander in https://github.com/FerretDB/FerretDB/pull/5466
### Other Changes 🤖
- Use middleware by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5377
- Use generated structs in Data API by @noisersup in https://github.com/FerretDB/FerretDB/pull/5382
- Upload YugabyteDB log files by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5401
- Update documentation to point to the next release by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5423
- Bump YugabyteDB to stable version by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5428
- Use QEMU on arm64 for Yugabyte for now by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5429
- Add comments to supervisord config by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5430
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5431
- Make `Run` wait for all `Handle` calls to finish by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5434
- Tweak `conninfo` by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5435
- Add middleware by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5437
- Update expected PostgreSQL version by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5438
- Avoid hijacking connections from the pool and immediately closing them by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5443
- Remove flaky test by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5444
- Add TODO comment about Debian 13 Trixie by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5450
- Add MCP tools by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5452
- Bump deps by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5455
- Update `wire` dependency by @noisersup in https://github.com/FerretDB/FerretDB/pull/5465
- Add helpers for lazy logging by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5467
- Move to `documentdb/documentdb` by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5468
- Update dependencies by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5479
- Add a test for `*documentdb.Conn` finalizing by @noisersup in https://github.com/FerretDB/FerretDB/pull/5484
- Finalize PostgreSQL metrics by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5485
- Fix spelling in `.go` files by @jsoref in https://github.com/FerretDB/FerretDB/pull/5492
- Fix `CODEOWNERS` grammar by @jsoref in https://github.com/FerretDB/FerretDB/pull/5495
- Use Go 1.25.1 by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5508
- Bump deps by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5509
- Minor observability cleanup by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5511
- Bump dependencies by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5519
- Use Go 1.25.3 by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5525
- Skip flaky test for now by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5526
- Use forked lazyerrors by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5527
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5528
- Use Go 1.25.4 by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5534
- Bump DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5536
- Bump deps by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5537
### New Contributors
- @jsoref made their first contribution in https://github.com/FerretDB/FerretDB/pull/5486
[All closed issues and pull requests](https://github.com/FerretDB/FerretDB/milestone/80?closed=1).
[All commits](https://github.com/FerretDB/FerretDB/compare/v2.5.0...v2.7.0).
## [v2.5.0](https://github.com/FerretDB/FerretDB/releases/tag/v2.5.0) (2025-08-12)
This version works best with
[DocumentDB v0.106.0-ferretdb-2.5.0](https://github.com/FerretDB/documentdb/releases/tag/v0.106.0-ferretdb-2.5.0).
### Breaking changes
#### Metric names
We changed Prometheus metric names and `HELP` texts to highlight the fact that they are
[not stable yet](https://docs.ferretdb.io/configuration/observability/#metrics).
We plan to promote most of them to stable in the next release.
### What's Changed
#### RPM packages for DocumentDB
DocumentDB `.rpm` packages for Red Hat Enterprise Linux are
[now available](https://docs.ferretdb.io/installation/documentdb/rpm/)!
### Documentation 📄
- Enable full visibility for images on zoom by @Fashander in https://github.com/FerretDB/FerretDB/pull/5346
- Add Vault blog post by @Fashander in https://github.com/FerretDB/FerretDB/pull/5368
- Add WeKan blog post by @Fashander in https://github.com/FerretDB/FerretDB/pull/5381
### Other Changes 🤖
- Add YugabyteDB integration tests by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5274
- Run all YugabyteDB integration tests by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5283
- Refactor normal and error responses by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5326
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5353
- Use Codecov for flaky integration tests detection by @noisersup in https://github.com/FerretDB/FerretDB/pull/5361
- Make accessing YugabyteDB logs easier by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5362
- Add components in codecov by @noisersup in https://github.com/FerretDB/FerretDB/pull/5364
- Skip YugabyteDB setup for `arm64` by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5372
- Tweak Ollama and MCPHost setup by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5375
- Simplify flags handling by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5378
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5380
- Extract wiring into a separate package by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5383
- Small cleanup by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5388
- Encapsulate PostgreSQL pool in the Handler by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5389
- Bump DocumentDB version by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5392
- Test flaky tests detection by @noisersup in https://github.com/FerretDB/FerretDB/pull/5393
- Small tweaks by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5394
- Make a resource.Untrack thread-safe by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5396
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5398
- Enable MongoDB test commands by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5400
- Update Prometheus metrics by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5403
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5411
[All closed issues and pull requests](https://github.com/FerretDB/FerretDB/milestone/79?closed=1).
[All commits](https://github.com/FerretDB/FerretDB/compare/v2.4.0...v2.5.0).
## [v2.4.0](https://github.com/FerretDB/FerretDB/releases/tag/v2.4.0) (2025-07-16)
This version works best with
[DocumentDB v0.105.0-ferretdb-2.4.0](https://github.com/FerretDB/documentdb/releases/tag/v0.105.0-ferretdb-2.4.0).
### New Features 🎉
- Allow `clusterAdmin` users to perform user management commands by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5314
### Enhancements 🛠
- Use debug-level log messages for continuations by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5266
### Documentation 📄
- Add blog post on setting up LibreChat with FerretDB by @Fashander in https://github.com/FerretDB/FerretDB/pull/4999
- Add blog post for Payload CMS by @Fashander in https://github.com/FerretDB/FerretDB/pull/5257
- Add blog post on using Compass GUI by @Fashander in https://github.com/FerretDB/FerretDB/pull/5263
- Fix social links by @Fashander in https://github.com/FerretDB/FerretDB/pull/5264
- Add PayloadCMS to compatible apps by @Fashander in https://github.com/FerretDB/FerretDB/pull/5270
- Add Compass to compatible apps by @Fashander in https://github.com/FerretDB/FerretDB/pull/5275
- Wrap long lines in a blog post by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5280
- Add NodeBB compatibility blog post by @Fashander in https://github.com/FerretDB/FerretDB/pull/5285
- Add Librechat to compatibility section by @Fashander in https://github.com/FerretDB/FerretDB/pull/5286
- Add NodeBB to compatible apps by @Fashander in https://github.com/FerretDB/FerretDB/pull/5293
- Cleanup NodeBB blog post by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5294
- Add Growthbook blogpost by @Fashander in https://github.com/FerretDB/FerretDB/pull/5304
- Add Heyform compatibility blog post by @Fashander in https://github.com/FerretDB/FerretDB/pull/5316
- Add GrowthBook to compatible apps by @Fashander in https://github.com/FerretDB/FerretDB/pull/5319
- Add DBeaver compatibility blogpost by @Fashander in https://github.com/FerretDB/FerretDB/pull/5321
- Add HyperDX blog post by @Fashander in https://github.com/FerretDB/FerretDB/pull/5334
- Add compatible applications by @Fashander in https://github.com/FerretDB/FerretDB/pull/5335
- Prepare v2.4.0 release by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5344
### Other Changes 🤖
- Add YugabyteDB to local setup by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5258
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5261
- Remove skipping of `bypassEmptyTsReplacement` by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5267
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5268
- Bring back Codecov and Coveralls integration by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5276
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5282
- Add `mcphost` as tool by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5287
- Refactor requests handling by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5292
- Add Dependabot configuration for mcphost by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5296
- Extend middleware requests and responses by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5301
- Minor refactoring of Data API by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5302
- Bump deps by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5307
- Refactor responses handling by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5310
- Add authentication test for client reconnecting by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5311
- Do not use deprecated method by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5312
- Update TODO URLs by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5317
- Remove `must.NotFail(wire[bson].NewXXX(` pattern by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5318
- Update TODO URL for `dropUsers` test by @chilagrow in https://github.com/FerretDB/FerretDB/pull/5324
- Avoid extra marshaling by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5327
- Split command handling and handlers by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5328
- Update DocumentDB by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5342
### New Contributors
[All closed issues and pull requests](https://github.com/FerretDB/FerretDB/milestone/77?closed=1).
[All commits](https://github.com/FerretDB/FerretDB/compare/v2.3.1...v2.4.0).
## [v2.3.1](https://github.com/FerretDB/FerretDB/releases/tag/v2.3.1) (2025-06-12)
Hotfix release to restore compatibility with various tools such as `mongoimport` and `mongorestore`.
### Fixed Bugs 🐛
- Revert "Remove hack for `bypassEmptyTsReplacement` (#5217)" by @AlekSi in https://github.com/FerretDB/FerretDB/pull/5256
[All closed issues and pull requests](https://github.com/FerretDB/FerretDB/milestone/78?closed=1).
[All commits](https://github.com/FerretDB/FerretDB/compare/v2.3.0...v2.3.1).
## [v2.3.0](https://github.com/FerretDB/FerretDB/releases/tag/v2.3.0) (2025-06-11)
This version works best with
+1 -1
View File
@@ -30,7 +30,7 @@ On macOS and Windows, [Docker Desktop](https://www.docker.com/products/docker-de
On Windows, it should be [configured to use WSL 2](https://docs.docker.com/desktop/windows/wsl/) without any distro;
all commands should be run on the host.
You will need Go 1.24 or later on the host.
You will need Go 1.25 or later on the host.
If your package manager doesn't provide it yet,
please install it from [go.dev](https://go.dev/dl/).
-2
View File
@@ -1,2 +0,0 @@
FerretDB database system (https://www.ferretdb.com/).
Copyright 2021 FerretDB Inc.
+1 -3
View File
@@ -19,7 +19,7 @@
FerretDB is an open-source alternative to MongoDB.
It is a proxy that converts MongoDB 5.0+ wire protocol queries to SQL
and uses PostgreSQL with [DocumentDB extension](https://github.com/microsoft/documentdb) as a database engine.
and uses PostgreSQL with [DocumentDB extension](https://github.com/documentdb/documentdb) as a database engine.
```mermaid
flowchart LR
@@ -116,7 +116,5 @@ See [there](https://pkg.go.dev/github.com/FerretDB/FerretDB/v2/build/version) fo
- [Slack chat](https://slack.ferretdb.io/) for quick questions.
- [GitHub Discussions](https://github.com/FerretDB/FerretDB/discussions) for longer topics.
- [GitHub Issues](https://github.com/FerretDB/FerretDB/issues) for bugs and missing features.
- [Open Office Hours meeting](https://calendar.google.com/calendar/event?action=TEMPLATE&tmeid=NGhrZTA5dXZ0MzQzN2gyaGVtZmx2aWxmN2pfMjAyNDA0MDhUMTcwMDAwWiBjX24zN3RxdW9yZWlsOWIwMm0wNzQwMDA3MjQ0QGc&tmsrc=c_n37tquoreil9b02m0740007244%40group.calendar.google.com&scp=ALL)
every Monday at 17:00 UTC at [Google Meet](https://meet.google.com/mcb-arhw-qbq).
If you want to contact FerretDB Inc., please use [this form](https://www.ferretdb.com/contact/).
+113 -9
View File
@@ -16,7 +16,9 @@ vars:
RACE_FLAG: -race={{and (ne OS "windows") (ne ARCH "arm") (ne ARCH "riscv64")}}
BUILD_TAGS: ferretdb_dev
MODE: diff-normal
SERVICES: postgres mongodb mongodb-secure jaeger
SERVICES: jaeger mongodb mongodb-secure postgres yugabytedb
LLM: qwen3:0.6b # sync with code
LLM_CONTEXT: 40960 # https://ollama.com/library/qwen3:0.6b
tasks:
# invoked when `task` is run without arguments
@@ -47,6 +49,13 @@ tasks:
- go mod verify
- go generate -x
init-tools-mcphost:
dir: tools/mcphost
cmds:
- go mod tidy
- go mod verify
- go generate -x
init-integration:
dir: integration
cmds:
@@ -55,7 +64,7 @@ tasks:
init:
desc: "Install development tools"
deps: [init-tools, init-tools-golangci, init-integration]
deps: [init-tools, init-tools-golangci, init-tools-mcphost, init-integration]
cmds:
- go mod tidy
- go mod verify
@@ -106,9 +115,13 @@ tasks:
- docker compose logs --follow
env-logs-collect:
desc: "Collect logs from Docker and save them to tmp/logs"
cmds:
- docker compose ps --all
- docker compose logs
- bin/envtool{{exeExt}} shell mkdir tmp/logs
- docker compose ps --all > tmp/logs/compose-ps.txt
- docker compose logs > tmp/logs/compose-logs.txt
- docker compose exec yugabytedb sh -c "bin/yugabyted collect_logs --stdout | gunzip | tar -xf - --one-top-level=/tmp/logs"
- docker compose cp yugabytedb:/tmp/logs tmp/logs/yugabytedb
env-up:
desc: "Start development environment"
@@ -187,7 +200,7 @@ tasks:
desc: "Run all unit tests"
cmds:
- go test -c -o /dev/null ./...
- go test -count=1 -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverprofile=cover.txt ./...
- go test -v -count=1 -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverprofile=cover.txt ./... | tee unit.txt
- go test -count=1 -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -bench=. -benchtime=1x ./...
- bin/task{{exeExt}} -d tools tools-test
@@ -251,6 +264,47 @@ tasks:
-target-backend=mongodb
-no-xfail={{.NO_XFAIL}}
test-integration-yugabytedb:
desc: "Run integration tests for FerretDB with YugabyteDB sequentially"
vars:
# DPanic on InternalError stops execution of remaining tests,
# ensure to complete all tests by not setting development build tag and without race detector.
# TODO https://github.com/FerretDB/FerretDB/issues/5397
BUILD_TAGS: ""
RACE_FLAG: -race=false
deps:
- task: gen-version
vars:
BUILD_TAGS:
ref: .BUILD_TAGS
RACE_FLAG:
ref: .RACE_FLAG
- task: test-integration-build
dir: integration
cmds:
- >
../bin/envtool{{exeExt}} tests run
--shard-index={{.SHARD_INDEX}}
--shard-total={{.SHARD_TOTAL}}
--run='{{.TEST_RUN}}'
--skip='{{.TEST_SKIP}}'
--raw-prefix=yugabytedb-{{.SHARD_INDEX}}-{{.SHARD_TOTAL}}
--
-count=1
-parallel=1
-p=1
-timeout={{.TEST_TIMEOUT}}
{{.RACE_FLAG}}
-tags={{.BUILD_TAGS}}
-shuffle=on
-coverpkg=../...
-coverprofile=integration-yugabytedb.txt
./...
-target-backend=ferretdb-yugabytedb
-postgresql-url='postgres://pg-user:pg-pass@127.0.0.1:5433/yugabyte'
-compat-url='mongodb://username:password@127.0.0.1:47017/?replicaSet=rs0'
-no-xfail={{.NO_XFAIL}}
bench-unit:
desc: "Run unit benchmarks"
cmds:
@@ -268,6 +322,7 @@ tasks:
--no-auth
--listen-addr=:27017
--listen-data-api-addr=127.0.0.1:8899
--listen-mcp-addr=127.0.0.1:8081
--proxy-addr=127.0.0.1:37017
--postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
--otel-traces-url=http://127.0.0.1:4318/v1/traces
@@ -282,12 +337,45 @@ tasks:
--mode={{.MODE}}
--listen-addr=:27017
--listen-data-api-addr=127.0.0.1:8899
--listen-mcp-addr=127.0.0.1:8081
--proxy-addr=127.0.0.1:47017
--postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
--otel-traces-url=http://127.0.0.1:4318/v1/traces
--dev-repl-set-name=rs0
--dev-records-dir=tmp/records
run-yugabytedb:
desc: "Run FerretDB with YugabyteDB without auth"
deps: [build-host]
cmds:
- >
bin/ferretdb{{exeExt}}
--mode={{.MODE}}
--no-auth
--listen-addr=:27017
--listen-data-api-addr=127.0.0.1:8899
--listen-mcp-addr=127.0.0.1:8081
--proxy-addr=127.0.0.1:37017
--postgresql-url='postgres://username:password@127.0.0.1:5433/yugabyte'
--otel-traces-url=http://127.0.0.1:4318/v1/traces
--dev-records-dir=tmp/records
run-yugabytedb-secure:
desc: "Run FerretDB with YugabyteDB with auth"
deps: [build-host]
cmds:
- >
bin/ferretdb{{exeExt}}
--mode={{.MODE}}
--listen-addr=:27017
--listen-data-api-addr=127.0.0.1:8899
--listen-mcp-addr=127.0.0.1:8081
--proxy-addr=127.0.0.1:47017
--postgresql-url='postgres://username:password@127.0.0.1:5433/yugabyte'
--otel-traces-url=http://127.0.0.1:4318/v1/traces
--dev-repl-set-name=rs0
--dev-records-dir=tmp/records
# invoked by FerretDB/github-actions/linters action
lint:
desc: "Run linters"
@@ -317,7 +405,7 @@ tasks:
security-trivy:
cmds:
- docker compose run --rm trivy --config=./build/trivy.yml filesystem .
- docker compose run --rm trivy --config=./build/trivy.yml filesystem --ignorefile=./build/.trivyignore .
godocs:
desc: "Serve Go code documentation"
@@ -325,18 +413,34 @@ tasks:
- bin/pkgsite{{exeExt}} -http=127.0.0.1:6060 -open
ollama:
desc: "Run Ollama server"
desc: "Run local Ollama server"
cmds:
- ollama serve
env:
# for https://ollama.com/library/phi4-mini
OLLAMA_CONTEXT_LENGTH: 131072
OLLAMA_CONTEXT_LENGTH: "{{.LLM_CONTEXT}}"
OLLAMA_DEBUG: 2
ollama-model:
desc: "Pull and load Ollama model"
cmds:
- ollama pull {{.LLM}}
- ollama run --think=false {{.LLM}} 'Hi!'
mcphost:
desc: "Run MCPHost for local Ollama server"
cmds:
- bin/mcphost --config=build/mcphost.json --model=ollama:{{.LLM}}
psql:
desc: "Run psql"
cmds:
- docker compose exec postgres psql -p 5432 -U username -d postgres
ysqlsh:
desc: "Run YugabyteDB SQL shell"
cmds:
- docker compose exec yugabytedb ./bin/ysqlsh postgres://username:password@host.docker.internal:5433/yugabyte
mongosh:
desc: "Run MongoDB shell (`mongosh`)"
cmds:
+7
View File
@@ -0,0 +1,7 @@
# https://github.com/cloudwego/eino#dependencies
# TODO https://github.com/cloudwego/eino/issues/291
CVE-2025-30153
# https://nvd.nist.gov/vuln/detail/CVE-2025-51471
# https://github.com/ollama/ollama/pull/10750
CVE-2025-51471
+1 -1
View File
@@ -1 +1 @@
FROM ghcr.io/ferretdb/ferretdb-prettier:3.5.3-1
FROM ghcr.io/ferretdb/ferretdb-prettier:3.6.2-1
+1 -1
View File
@@ -1 +1 @@
FROM ghcr.io/ferretdb/ferretdb-textlint:14.7.1-1
FROM ghcr.io/ferretdb/ferretdb-textlint:15.2.0-1
+1 -1
View File
@@ -1 +1 @@
FROM ghcr.io/ferretdb/ferretdb-wrangler:4.7.1-1
FROM ghcr.io/ferretdb/ferretdb-wrangler:4.24.0-1
+1 -1
View File
@@ -1 +1 @@
FROM jaegertracing/all-in-one:1.69.0
FROM jaegertracing/all-in-one:1.74.0
-1
View File
@@ -1 +0,0 @@
FROM ghcr.io/danny-avila/librechat:v0.7.8
+1 -1
View File
@@ -1,5 +1,5 @@
# Use development image and full tag close to the release.
FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-0.104.0-ferretdb-2.3.0
FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-0.107.0-ferretdb-2.7.0
# Use moving development image during development.
# FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-ferretdb
+1 -1
View File
@@ -1,2 +1,2 @@
FROM aquasec/trivy:0.63.0
FROM aquasec/trivy:0.67.2
WORKDIR /workdir
-1
View File
@@ -1 +0,0 @@
FROM yugabytedb/yugabyte:2.25.2.0-b359
+1
View File
@@ -0,0 +1 @@
FROM yugabytedb/yugabyte:2025.1.1.0-b197
+4 -4
View File
@@ -1,5 +1,5 @@
# Use production image and full tag close to the release.
FROM --platform=linux/amd64 ghcr.io/ferretdb/postgres-documentdb:17-0.104.0-ferretdb-2.3.0
FROM --platform=linux/amd64 ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0
# Use moving development image during development.
# FROM --platform=linux/amd64 ghcr.io/ferretdb/postgres-documentdb-dev:17-ferretdb
@@ -15,9 +15,9 @@ apt update
apt install -y git-lfs mongodb-mongosh tmux nano
cd /tmp
curl -OJL https://go.dev/dl/go1.24.4.linux-amd64.tar.gz
tar -xf go1.24.4.linux-amd64.tar.gz
rm go1.24.4.linux-amd64.tar.gz
curl -OJL https://go.dev/dl/go1.25.4.linux-amd64.tar.gz
tar -xf go1.25.4.linux-amd64.tar.gz
rm go1.25.4.linux-amd64.tar.gz
mv go /usr/local/
ln -s /usr/local/go/bin/* /usr/local/bin/
go version
+6 -3
View File
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
# prepare stage
FROM --platform=$BUILDPLATFORM golang:1.24.4 AS development-prepare
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM --platform=$BUILDPLATFORM golang:1.25.4-bookworm AS development-prepare
# use a single directory for all Go caches to simplify RUN --mount commands below
ENV GOPATH=/cache/gopath
@@ -36,7 +37,8 @@ EOF
# build stage
FROM golang:1.24.4 AS development-build
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM golang:1.25.4-bookworm AS development-build
ARG TARGETARCH
@@ -103,7 +105,8 @@ COPY --from=development-build /src/bin/ferretdb /ferretdb
# final stage
FROM golang:1.24.4 AS development
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM golang:1.25.4-bookworm AS development
ENV GOCOVERDIR=/tmp/cover
ENV GORACE=halt_on_error=1,history_size=2
+5 -3
View File
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
# prepare stage
FROM --platform=$BUILDPLATFORM golang:1.24.4 AS eval-dev-prepare
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM --platform=$BUILDPLATFORM golang:1.25.4-bookworm AS eval-dev-prepare
# use a single directory for all Go caches to simplify RUN --mount commands below
ENV GOPATH=/cache/gopath
@@ -36,7 +37,8 @@ EOF
# build stage
FROM golang:1.24.4 AS eval-dev-build
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM golang:1.25.4-bookworm AS eval-dev-build
ARG TARGETARCH
@@ -97,7 +99,7 @@ EOF
# final stage
# Use development image and full tag close to the release.
FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-0.104.0-ferretdb-2.3.0 AS eval-dev
FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-0.107.0-ferretdb-2.7.0 AS eval-dev
# Use moving development image during development.
# FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-ferretdb AS eval-dev
+5 -3
View File
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
# prepare stage
FROM --platform=$BUILDPLATFORM golang:1.24.4 AS eval-prepare
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM --platform=$BUILDPLATFORM golang:1.25.4-bookworm AS eval-prepare
# use a single directory for all Go caches to simplify RUN --mount commands below
ENV GOPATH=/cache/gopath
@@ -36,7 +37,8 @@ EOF
# build stage
FROM golang:1.24.4 AS eval-build
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM golang:1.25.4-bookworm AS eval-build
ARG TARGETARCH
@@ -89,7 +91,7 @@ EOF
# final stage
# Use production image and full tag close to the release.
FROM ghcr.io/ferretdb/postgres-documentdb:17-0.104.0-ferretdb-2.3.0 AS eval
FROM ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0 AS eval
# Use moving development image during development.
# FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-ferretdb AS eval
+4 -3
View File
@@ -1,11 +1,11 @@
; https://supervisord.org/configuration.html
; https://supervisord.org/configuration.html#supervisord-section-values
[supervisord]
nodaemon=true
user=root
loglevel=info
logfile=/dev/stderr
logfile_maxbytes=0
; https://supervisord.org/configuration.html#program-x-section-values
[program:postgres]
command=/usr/local/bin/docker-entrypoint.sh postgres
; lower priority postgres starts before ferretdb (default priority 999)
@@ -16,6 +16,7 @@ stdout_logfile=/dev/stderr
stdout_logfile_maxbytes=0
redirect_stderr=true
; https://supervisord.org/configuration.html#program-x-section-values
[program:ferretdb]
command=/usr/local/bin/ferretdb
stdout_logfile=/dev/stderr
+4 -2
View File
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
# prepare stage
FROM --platform=$BUILDPLATFORM golang:1.24.4 AS production-prepare
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM --platform=$BUILDPLATFORM golang:1.25.4-bookworm AS production-prepare
# use a single directory for all Go caches to simplify RUN --mount commands below
ENV GOPATH=/cache/gopath
@@ -36,7 +37,8 @@ EOF
# build stage
FROM golang:1.24.4 AS production-build
# TODO https://github.com/FerretDB/FerretDB/issues/5449
FROM golang:1.25.4-bookworm AS production-build
ARG TARGETARCH
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"FerretDB": {
"type": "remote",
"url": "http://127.0.0.1:8081/mcp"
}
}
}
+3
View File
@@ -1,3 +1,6 @@
setParameter:
enableTestCommands: 1 # for sleep
net:
port: 47017 # matches host's port for replica set to work
bindIpAll: true
+3
View File
@@ -1,3 +1,6 @@
setParameter:
enableTestCommands: 1 # for sleep
net:
port: 37017 # matches host's port for replica set to work
bindIpAll: true
+10 -7
View File
@@ -18,17 +18,20 @@ import "runtime"
const (
// DocumentDB is a version of DocumentDB this version of FerretDB is compatible with.
DocumentDB = "0.104.0 gitref: HEAD sha:2045d0e buildId:0"
DocumentDB = "0.107.0 gitref: HEAD sha:e63835403d buildId:0"
// DocumentDBURL points to the release page of the DocumentDB version above.
DocumentDBURL = "https://github.com/FerretDB/documentdb/releases/tag/v0.104.0-ferretdb-2.3.0"
DocumentDBURL = "https://github.com/FerretDB/documentdb/releases/tag/v0.107.0-ferretdb-2.7.0"
)
// DocumentDBSafeToUpdate represents versions of DocumentDB that FerretDB can update.
var DocumentDBSafeToUpdate = []string{
"0.102.0 gitref: HEAD sha:80462f5 buildId:0", // v2.1.0
"0.103.0 gitref: HEAD sha:7514232 buildId:0", // v2.2.0
"0.104.0 gitref: HEAD sha:2045d0e buildId:0", // v2.3.0
"0.102.0 gitref: HEAD sha:80462f5 buildId:0", // v2.1.0
"0.103.0 gitref: HEAD sha:7514232 buildId:0", // v2.2.0
"0.104.0 gitref: HEAD sha:2045d0e buildId:0", // v2.3.0, v2.3.1
"0.105.0 gitref: HEAD sha:8453d93b buildId:0", // v2.4.0
"0.106.0 gitref: HEAD sha:beb9d25d98 buildId:0", // v2.5.0
// FerretDB v2.6.0 wasn't released
}
// PostgreSQLTest is a version of PostgreSQL used by tests.
@@ -40,6 +43,6 @@ func init() {
arch = "aarch64-unknown-linux-gnu"
}
PostgreSQLTest = "PostgreSQL 17.5 (Debian 17.5-1.pgdg120+1) on " + arch + ", " +
"compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit"
PostgreSQLTest = "PostgreSQL 17.6 (Debian 17.6-2.pgdg12+1) on " + arch + ", " +
"compiled by gcc (Debian 12.2.0-14+deb12u1) 12.2.0, 64-bit"
}
+1 -1
View File
@@ -93,7 +93,7 @@ const ferretdbModule = "github.com/FerretDB/FerretDB/v2"
// semVerTag is a https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string,
// but with a leading `v`.
//
//nolint:lll // for readibility
//nolint:lll // for readability
var semVerTag = regexp.MustCompile(`^v(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
// Get returns current build's info.
+1
View File
@@ -0,0 +1 @@
CREATE EXTENSION IF NOT EXISTS documentdb CASCADE;
+1 -1
View File
@@ -261,7 +261,7 @@ func main() {
switch cmd {
case "setup":
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
err = setup(ctx, logger)
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"slices"
"strings"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/AlekSi/lazyerrors"
)
// copyFile copies a file from src to dst, overwriting dst if it exists.
+94 -2
View File
@@ -23,12 +23,16 @@ import (
"net/url"
"time"
"github.com/AlekSi/lazyerrors"
"github.com/FerretDB/wire/wirebson"
"github.com/jackc/pgx/v5"
"github.com/prometheus/client_golang/prometheus"
"github.com/FerretDB/FerretDB/v2/internal/documentdb"
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
"github.com/FerretDB/FerretDB/v2/internal/util/debug"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/state"
)
// setupMongoDB configures MongoDB containers.
@@ -68,6 +72,84 @@ func setupMongoDB(ctx context.Context, logger *slog.Logger, uri, name string) er
return ctx.Err()
}
// setupUser waits for the given PostgreSQL URI to become available, and creates
// an admin user with fixed credentials `username:password` using DocumentDB API.
func setupUser(ctx context.Context, uri string, l *slog.Logger) error {
sp, err := state.NewProvider("")
if err != nil {
return lazyerrors.Error(err)
}
pool, err := documentdb.NewPool(uri, l, sp)
if err != nil {
return lazyerrors.Error(err)
}
defer pool.Close()
var retry int64
createUser := wirebson.MustDocument(
"createUser", "username",
"pwd", "password",
"roles", wirebson.MustArray(
wirebson.MustDocument(
"role", "clusterAdmin",
"db", "admin",
),
wirebson.MustDocument(
"role", "readWriteAnyDatabase",
"db", "admin",
),
),
)
var res wirebson.RawDocument
for ctx.Err() == nil {
err = pool.WithConn(func(conn *pgx.Conn) error {
res, err = documentdb.CreateUser(ctx, conn, l, createUser)
return err
})
if err == nil {
break
}
l.InfoContext(ctx, "Waiting for DocumentDB extension to be created")
retry++
ctxutil.SleepWithJitter(ctx, time.Second, retry)
}
if err != nil {
return lazyerrors.Error(err)
}
d, err := res.Decode()
if err != nil {
return lazyerrors.Error(err)
}
switch ok := d.Get("ok").(type) {
case float64:
if ok != float64(1) {
return lazyerrors.Errorf("Failed to create user: %s", d.LogMessage())
}
case int32:
// TODO https://github.com/FerretDB/FerretDB/issues/5313
if ok != int32(1) {
return lazyerrors.Errorf("Failed to create user: %s", d.LogMessage())
}
default:
panic("unexpected type")
}
l.InfoContext(ctx, "User created", slog.String("response", d.LogMessage()))
return nil
}
// setup runs all setup commands.
func setup(ctx context.Context, logger *slog.Logger) error {
h, err := debug.Listen(&debug.ListenOpts{
@@ -79,12 +161,22 @@ func setup(ctx context.Context, logger *slog.Logger) error {
return lazyerrors.Error(err)
}
go h.Serve(ctx)
go h.Run(ctx)
if err = setupMongoDB(ctx, logger, "mongodb://username:password@127.0.0.1:47017/", "mongodb-secure"); err != nil {
return lazyerrors.Error(err)
}
uri := "postgres://pg-user:pg-pass@127.0.0.1:5432/postgres"
if err = setupUser(ctx, uri, logging.WithName(logger, "postgres")); err != nil {
return lazyerrors.Error(err)
}
uri = "postgres://pg-user:pg-pass@127.0.0.1:5433/yugabyte"
if err = setupUser(ctx, uri, logging.WithName(logger, "yugabytedb")); err != nil {
return lazyerrors.Error(err)
}
logger.InfoContext(ctx, "Done")
return nil
}
+6 -7
View File
@@ -31,12 +31,12 @@ import (
"strings"
"time"
"github.com/AlekSi/lazyerrors"
"go.opentelemetry.io/otel"
otelattribute "go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/must"
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
)
@@ -72,7 +72,7 @@ type testResult struct {
// listTestFuncs returns a sorted slice of all top-level test functions (tests, benchmarks, examples, fuzz functions)
// matching given regular expression in the specified directory and subdirectories.
func listTestFuncs(ctx context.Context, dir, re string, logger *slog.Logger) ([]string, error) {
ctx, span := otel.Tracer("").Start(ctx, "listTestFuncs")
ctx, span := otel.Tracer("").Start(ctx, "envtool.listTestFuncs")
defer span.End()
var buf bytes.Buffer
@@ -158,7 +158,7 @@ func shardTestFuncs(index, total uint, testFuncs []string) ([]string, error) {
// testArgs handles `envtool tests run` arguments and returns a slice of `go test` arguments.
func testArgs(ctx context.Context, dir string, index, total uint, run, skip string, logger *slog.Logger) ([]string, uint, error) {
ctx, span := otel.Tracer("").Start(ctx, "testArgs")
ctx, span := otel.Tracer("").Start(ctx, "envtool.testArgs")
defer span.End()
listRE := "."
@@ -309,7 +309,7 @@ func runGoTest(runCtx context.Context, opts *runGoTestOpts) (resErr error) {
totalTests = strconv.Itoa(int(opts.total))
}
runCtx, runSpan := otel.Tracer("").Start(runCtx, "run")
runCtx, runSpan := otel.Tracer("").Start(runCtx, "envtool.runGoTest")
runSpan.SetAttributes(otelattribute.String("db.ferretdb.envtool.total_tests", totalTests))
defer runSpan.End()
@@ -377,7 +377,6 @@ func runGoTest(runCtx context.Context, opts *runGoTestOpts) (resErr error) {
key = resultKey(event.Package, parentTest(event.Test))
parent := results[key]
// TODO https://github.com/FerretDB/FerretDB/issues/4465
if parent == nil {
panic(fmt.Sprintf(
"no parent test found: package=%q, test=%q, key=%q", event.Package, event.Test, key,
@@ -585,14 +584,14 @@ func testsRun(ctx context.Context, params *TestsRunParams, logger *slog.Logger)
ot, err := observability.NewOTelTraceExporter(&observability.OTelTraceExporterOpts{
Logger: logger,
Service: "envtool-tests",
URL: "http://127.0.0.1:4318/v1/traces",
Service: "envtool-tests",
})
if err != nil {
return lazyerrors.Error(err)
}
ctx, span := otel.Tracer("").Start(ctx, "testsRun")
ctx, span := otel.Tracer("").Start(ctx, "envtool.testsRun")
ctx, cancel := context.WithCancel(ctx)
done := make(chan struct{})
+62 -108
View File
@@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package main is the entry point for FerretDB server.
package main
import (
"cmp"
"context"
"encoding/json"
"expvar"
"fmt"
"log"
"log/slog"
"math"
"os"
"runtime"
runtimedebug "runtime/debug"
@@ -34,15 +33,11 @@ import (
"github.com/alecthomas/kong"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/expfmt"
"go.uber.org/automaxprocs/maxprocs"
_ "golang.org/x/crypto/x509roots/fallback" // register root TLS certificates for production Docker image
"github.com/FerretDB/FerretDB/v2/build/version"
"github.com/FerretDB/FerretDB/v2/internal/clientconn"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/v2/internal/dataapi"
"github.com/FerretDB/FerretDB/v2/internal/documentdb"
"github.com/FerretDB/FerretDB/v2/internal/handler"
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
"github.com/FerretDB/FerretDB/v2/internal/util/debug"
"github.com/FerretDB/FerretDB/v2/internal/util/devbuild"
@@ -50,6 +45,7 @@ import (
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/must"
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
"github.com/FerretDB/FerretDB/v2/internal/util/setup"
"github.com/FerretDB/FerretDB/v2/internal/util/state"
"github.com/FerretDB/FerretDB/v2/internal/util/telemetry"
)
@@ -78,6 +74,7 @@ var cli struct {
TLSKeyFile string `default:"" help:"TLS key file path."`
TLSCaFile string `default:"" help:"TLS CA file path."`
DataAPIAddr string `default:"" help:"Listen TCP address for HTTP Data API."`
MCPAddr string `default:"" help:"Listen TCP address for HTTP MCP server."`
} `embed:"" prefix:"listen-" group:"Interfaces"`
Proxy struct {
@@ -102,7 +99,8 @@ var cli struct {
MetricsUUID bool `default:"false" help:"Add instance UUID to all metrics." group:"Miscellaneous" negatable:""`
OTel struct {
Traces struct {
ServiceName string `default:"ferretdb" help:"OpenTelemetry service name."`
Traces struct {
URL string `default:"" help:"OpenTelemetry OTLP/HTTP traces endpoint URL (e.g. 'http://host:4318/v1/traces')."`
} `embed:"" prefix:"traces-"`
} `embed:"" prefix:"otel-" group:"Miscellaneous"`
@@ -137,14 +135,14 @@ var (
kongOptions = []kong.Option{
kong.Vars{
"default_log_level": defaultLogLevel().String(),
"default_mode": clientconn.AllModes[0],
"default_mode": middleware.AllModes[0],
"enum_log_format": strings.Join(logFormats, ","),
"enum_mode": strings.Join(clientconn.AllModes, ","),
"enum_mode": strings.Join(middleware.AllModes, ","),
"help_log_format": fmt.Sprintf("Log format: '%s'.", strings.Join(logFormats, "', '")),
"help_log_level": fmt.Sprintf("Log level: '%s'.", strings.Join(logLevels, "', '")),
"help_mode": fmt.Sprintf("Operation mode: '%s'.", strings.Join(clientconn.AllModes, "', '")),
"help_mode": fmt.Sprintf("Operation mode: '%s'.", strings.Join(middleware.AllModes, "', '")),
"help_telemetry": "Enable or disable basic telemetry reporting. See https://beacon.ferretdb.com.",
},
kong.DefaultEnvars("FERRETDB"),
@@ -248,7 +246,23 @@ func setupDefaultLogger(format string, uuid string) *slog.Logger {
}
// checkFlags checks that CLI flags are not self-contradictory and produces warnings if needed.
// It also replaces "-" with "" for some flags to make it easier to configure FerretDB with environment variables.
func checkFlags(logger *slog.Logger) {
// keep in sync with documentation
for _, p := range []*string{
&cli.Listen.Addr,
&cli.Listen.Unix,
&cli.Listen.TLS,
&cli.Listen.DataAPIAddr,
&cli.Listen.MCPAddr,
&cli.DebugAddr,
&cli.OTel.Traces.URL,
} {
if *p == "-" {
*p = ""
}
}
ctx := context.Background()
if devbuild.Enabled {
@@ -262,6 +276,13 @@ func checkFlags(logger *slog.Logger) {
if !cli.Auth {
logger.WarnContext(ctx, "Authentication is disabled; the server will accept any connection")
}
if cli.Auth && cli.Listen.MCPAddr != "" {
logger.Log(
ctx, logging.LevelFatal,
"MCP server does not support authentication; please disable authentication or MCP server",
)
}
}
// dumpMetrics dumps all Prometheus metrics to stderr.
@@ -352,19 +373,6 @@ func run() {
checkFlags(logger)
maxprocsOpts := []maxprocs.Option{
maxprocs.Min(2),
maxprocs.RoundQuotaFunc(func(v float64) int {
return int(math.Ceil(v))
}),
maxprocs.Logger(func(format string, a ...any) {
logger.Info(fmt.Sprintf(format, a...))
}),
}
if _, err = maxprocs.Set(maxprocsOpts...); err != nil {
logger.Warn("Failed to set GOMAXPROCS", logging.Error(err))
}
ctx, stop := ctxutil.SigTerm(context.Background())
go func() {
@@ -380,7 +388,7 @@ func run() {
var wg sync.WaitGroup
if cmp.Or(cli.DebugAddr, "-") != "-" {
if cli.DebugAddr != "" {
wg.Add(1)
go func() {
@@ -409,11 +417,11 @@ func run() {
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create debug handler", logging.Error(e))
}
h.Serve(ctx)
h.Run(ctx)
}()
}
if cmp.Or(cli.OTel.Traces.URL, "-") != "-" {
if cli.OTel.Traces.URL != "" {
wg.Add(1)
go func() {
@@ -423,9 +431,8 @@ func run() {
ot, e := observability.NewOTelTraceExporter(&observability.OTelTraceExporterOpts{
Logger: l,
Service: "ferretdb",
Version: version.Get().Version,
URL: cli.OTel.Traces.URL,
Service: cli.OTel.ServiceName,
})
if e != nil {
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create Otel tracer", logging.Error(e))
@@ -435,7 +442,7 @@ func run() {
}()
}
lm := connmetrics.NewListenerMetrics()
mm := middleware.NewMetrics()
{
wg.Add(1)
@@ -452,7 +459,7 @@ func run() {
DNT: os.Getenv("DO_NOT_TRACK"),
ExecName: os.Args[0],
P: stateProvider,
ConnMetrics: lm.ConnMetrics,
Metrics: mm,
L: l,
UndecidedDelay: cli.Dev.Telemetry.UndecidedDelay,
ReportInterval: cli.Dev.Telemetry.ReportInterval,
@@ -465,97 +472,44 @@ func run() {
}()
}
p, err := documentdb.NewPool(cli.PostgreSQLURL, logging.WithName(logger, "pool"), stateProvider)
if err != nil {
logger.LogAttrs(ctx, logging.LevelFatal, "Failed to construct pool", logging.Error(err))
}
tcpAddr := cli.Listen.Addr
if cmp.Or(tcpAddr, "-") == "-" {
tcpAddr = ""
}
unixAddr := cli.Listen.Unix
if cmp.Or(unixAddr, "-") == "-" {
unixAddr = ""
}
tlsAddr := cli.Listen.TLS
if cmp.Or(tlsAddr, "-") == "-" {
tlsAddr = ""
}
handlerOpts := &handler.NewOpts{
Pool: p,
Auth: cli.Auth,
TCPHost: tcpAddr,
ReplSetName: cli.Dev.ReplSetName,
L: logging.WithName(logger, "handler"),
ConnMetrics: lm.ConnMetrics,
//exhaustruct:enforce
res := setup.Setup(ctx, &setup.SetupOpts{
Logger: logger,
StateProvider: stateProvider,
}
Metrics: mm,
h, err := handler.New(handlerOpts)
if err != nil {
p.Close()
handlerOpts.L.LogAttrs(ctx, logging.LevelFatal, "Failed to construct handler", logging.Error(err))
}
PostgreSQLURL: cli.PostgreSQLURL,
Auth: cli.Auth,
ReplSetName: cli.Dev.ReplSetName,
SessionCleanupInterval: 0,
lis, err := clientconn.Listen(&clientconn.ListenerOpts{
Handler: h,
Metrics: lm,
Logger: logger,
TCP: tcpAddr,
Unix: unixAddr,
TLS: tlsAddr,
TLSCertFile: cli.Listen.TLSCertFile,
TLSKeyFile: cli.Listen.TLSKeyFile,
TLSCAFile: cli.Listen.TLSCaFile,
Mode: clientconn.Mode(cli.Mode),
ProxyAddr: cli.Proxy.Addr,
ProxyTLSCertFile: cli.Proxy.TLSCertFile,
ProxyTLSKeyFile: cli.Proxy.TLSKeyFile,
ProxyTLSCAFile: cli.Proxy.TLSCaFile,
TCPAddr: cli.Listen.Addr,
UnixAddr: cli.Listen.Unix,
TLSAddr: cli.Listen.TLS,
TLSCertFile: cli.Listen.TLSCertFile,
TLSKeyFile: cli.Listen.TLSKeyFile,
TLSCAFile: cli.Listen.TLSCaFile,
Mode: middleware.Mode(cli.Mode),
TestRecordsDir: cli.Dev.RecordsDir,
DataAPIAddr: cli.Listen.DataAPIAddr,
MCPAddr: cli.Listen.MCPAddr,
})
if err != nil {
p.Close()
logger.LogAttrs(ctx, logging.LevelFatal, "Failed to construct listener", logging.Error(err))
if res == nil {
os.Exit(1)
}
if cmp.Or(cli.Listen.DataAPIAddr, "-") != "-" {
wg.Add(1)
listener.Store(res.WireListener)
go func() {
defer wg.Done()
metricsRegisterer.MustRegister(res)
l := logging.WithName(logger, "dataapi")
lis, e := dataapi.Listen(&dataapi.ListenOpts{
TCPAddr: cli.Listen.DataAPIAddr,
L: l,
Handler: h,
})
if e != nil {
p.Close()
l.LogAttrs(ctx, logging.LevelFatal, "Failed to construct DataAPI listener", logging.Error(e))
}
lis.Run(ctx)
}()
}
listener.Store(lis)
metricsRegisterer.MustRegister(lis)
lis.Run(ctx)
res.Run(ctx)
wg.Wait()
+1 -1
View File
@@ -39,7 +39,7 @@ func TestVersion(t *testing.T) {
ctx, cancel := context.WithTimeout(testutil.Ctx(t), 5*time.Second)
t.Cleanup(cancel)
bin := filepath.Join("..", "..", "bin", "ferretdb")
bin := filepath.Join(testutil.BinDir, "ferretdb")
cmd := exec.CommandContext(ctx, bin, "--version")
b, err := cmd.Output()
+10 -5
View File
@@ -15,7 +15,6 @@
package main
import (
"cmp"
"context"
"fmt"
"log/slog"
@@ -39,7 +38,7 @@ type ReadyZ struct {
func (r *ReadyZ) Probe(ctx context.Context) bool {
var urls []string
if cmp.Or(cli.Listen.Addr, "-") != "-" {
if cli.Listen.Addr != "" {
host, port, err := net.SplitHostPort(cli.Listen.Addr)
if err != nil {
r.l.ErrorContext(ctx, "Getting host and port failed", logging.Error(err))
@@ -59,12 +58,12 @@ func (r *ReadyZ) Probe(ctx context.Context) bool {
urls = append(urls, u.String())
}
if cmp.Or(cli.Listen.TLS, "-") != "-" {
if cli.Listen.TLS != "" {
// TODO https://github.com/FerretDB/FerretDB/issues/4427
r.l.WarnContext(ctx, "TLS ping is not implemented yet")
}
if cmp.Or(cli.Listen.Unix, "-") != "-" {
if cli.Listen.Unix != "" {
urls = append(urls, "mongodb://"+url.PathEscape(cli.Listen.Unix))
}
@@ -76,7 +75,13 @@ func (r *ReadyZ) Probe(ctx context.Context) bool {
for _, u := range urls {
r.l.DebugContext(ctx, fmt.Sprintf("Pinging %s", u))
conn, err := wireclient.Connect(ctx, u, r.l)
cleanUri, _, _, _, err := wireclient.Credentials(u)
if err != nil {
r.l.ErrorContext(ctx, "Getting credentials failed", logging.Error(err))
return false
}
conn, err := wireclient.Connect(ctx, cleanUri, r.l)
if err != nil {
r.l.ErrorContext(ctx, "Connection failed", logging.Error(err))
return false
+28 -8
View File
@@ -20,12 +20,38 @@ services:
environment:
# UTC−03:30/−02:30. Set to catch timezone problems.
- TZ=America/St_Johns
- POSTGRES_USER=username
- POSTGRES_PASSWORD=password
- POSTGRES_USER=pg-user
- POSTGRES_PASSWORD=pg-pass
- POSTGRES_DB=postgres
volumes:
- ./build/testscripts:/testscripts
yugabytedb:
build:
context: ./build/deps
dockerfile: yugabytedb.Dockerfile
container_name: ferretdb_yugabytedb
platform: linux/amd64 # TODO https://github.com/FerretDB/FerretDB/issues/5369
# postgresql.conf is populated by "ysql_pg_conf_csv" flag
# https://docs.yugabyte.com/preview/reference/configuration/yugabyted/#pass-additional-flags-to-yb-master-and-yb-tserver
command: >
bin/yugabyted start
--background=false
--initial_scripts_dir=/yugabytedb
--ysql_enable_auth=true
--master_flags="allowed_preview_flags_csv=ysql_enable_documentdb,ysql_enable_documentdb=true,enable_pg_cron=true,ysql_pg_conf_csv={documentdb.enableUserCrud = true,documentdb.maxUserLimit = 100}"
--tserver_flags="allowed_preview_flags_csv=ysql_enable_documentdb,ysql_enable_documentdb=true,enable_pg_cron=true,ysql_pg_conf_csv={documentdb.enableUserCrud = true,documentdb.maxUserLimit = 100}"
ports:
- 5433:5433
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- YSQL_USER=pg-user # this user cannot authenticate against FerretDB
- YSQL_PASSWORD=pg-pass
- YSQL_DB=yugabyte # DocumentDB extension is installed in yugabyte database
volumes:
- ./build/yugabytedb/init.sql:/yugabytedb/init.sql
mongodb:
build:
context: ./build/deps
@@ -98,12 +124,6 @@ services:
- 6831:6831/udp # Compact Thrift from BuildKit
- 16686:16686 # UI on http://127.0.0.1:16686/
librechat:
build:
context: ./build/deps
dockerfile: librechat.Dockerfile
container_name: librechat_jaeger
trivy:
build:
context: ./build/deps
+36 -42
View File
@@ -35,11 +35,9 @@ import (
"time"
"github.com/FerretDB/FerretDB/v2/build/version"
"github.com/FerretDB/FerretDB/v2/internal/clientconn"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/v2/internal/documentdb"
"github.com/FerretDB/FerretDB/v2/internal/handler"
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/setup"
"github.com/FerretDB/FerretDB/v2/internal/util/state"
"github.com/FerretDB/FerretDB/v2/internal/util/telemetry"
)
@@ -73,8 +71,8 @@ type Config struct {
// FerretDB represents an instance of embedded FerretDB implementation.
type FerretDB struct {
tl *telemetry.Reporter
lis *clientconn.Listener
tr *telemetry.Reporter
res *setup.SetupResult
}
// New creates a new instance of embedded FerretDB implementation.
@@ -106,7 +104,7 @@ func New(config *Config) (*FerretDB, error) {
}
logger := logging.WithName(logging.Logger(logOutput, lOpts, ""), "ferretdb")
lm := connmetrics.NewListenerMetrics()
mm := middleware.NewMetrics()
tr, err := telemetry.NewReporter(&telemetry.NewReporterOpts{
URL: "https://beacon.ferretdb.com/",
@@ -115,7 +113,7 @@ func New(config *Config) (*FerretDB, error) {
DNT: os.Getenv("DO_NOT_TRACK"),
ExecName: os.Args[0],
P: stateProvider,
ConnMetrics: lm.ConnMetrics,
Metrics: mm,
L: logging.WithName(logger, "telemetry"),
UndecidedDelay: time.Hour,
ReportInterval: 24 * time.Hour,
@@ -124,46 +122,42 @@ func New(config *Config) (*FerretDB, error) {
return nil, fmt.Errorf("failed to create telemetry reporter: %w", err)
}
p, err := documentdb.NewPool(config.PostgreSQLURL, logging.WithName(logger, "pool"), stateProvider)
if err != nil {
return nil, fmt.Errorf("failed to construct pool: %w", err)
}
handlerOpts := &handler.NewOpts{
Pool: p,
Auth: false,
TCPHost: "",
ReplSetName: "",
L: logging.WithName(logger, "handler"),
ConnMetrics: lm.ConnMetrics,
//exhaustruct:enforce
res := setup.Setup(context.TODO(), &setup.SetupOpts{
Logger: logger,
StateProvider: stateProvider,
}
Metrics: mm,
h, err := handler.New(handlerOpts)
if err != nil {
p.Close()
return nil, fmt.Errorf("failed to construct handler: %w", err)
}
PostgreSQLURL: config.PostgreSQLURL,
Auth: false,
ReplSetName: "",
SessionCleanupInterval: 0,
lis, err := clientconn.Listen(&clientconn.ListenerOpts{
Handler: h,
Metrics: lm,
Logger: logger,
ProxyAddr: "",
ProxyTLSCertFile: "",
ProxyTLSKeyFile: "",
ProxyTLSCAFile: "",
TCP: config.ListenAddr,
TCPAddr: config.ListenAddr,
UnixAddr: "",
TLSAddr: "",
TLSCertFile: "",
TLSKeyFile: "",
TLSCAFile: "",
Mode: middleware.NormalMode,
TestRecordsDir: "",
Mode: clientconn.NormalMode,
DataAPIAddr: "",
MCPAddr: "",
})
if err != nil {
p.Close()
return nil, fmt.Errorf("failed to construct listener: %w", err)
if res == nil {
return nil, fmt.Errorf("failed to create FerretDB")
}
return &FerretDB{
tl: tr,
lis: lis,
tr: tr,
res: res,
}, nil
}
@@ -180,14 +174,14 @@ func (f *FerretDB) Run(ctx context.Context) {
go func() {
defer wg.Done()
f.tl.Run(ctx)
f.tr.Run(ctx)
}()
wg.Add(1)
go func() {
defer wg.Done()
f.lis.Run(ctx)
f.res.Run(ctx)
}()
wg.Wait()
@@ -197,7 +191,7 @@ func (f *FerretDB) Run(ctx context.Context) {
func (f *FerretDB) MongoDBURI() string {
u := &url.URL{
Scheme: "mongodb",
Host: f.lis.TCPAddr().String(),
Host: f.res.WireListener.TCPAddr().String(),
Path: "/",
}
+1 -1
View File
@@ -89,7 +89,7 @@ func TestFerretDB(t *testing.T) {
ListenAddr: "127.0.0.1:0",
StateDir: t.TempDir(),
LogLevel: slog.LevelDebug,
LogOutput: testutil.NewLogWriter(t),
LogOutput: t.Output(),
})
require.NoError(t, err)
+41 -36
View File
@@ -1,68 +1,73 @@
module github.com/FerretDB/FerretDB/v2
go 1.24
go 1.25
toolchain go1.24.4
toolchain go1.25.4
// replace github.com/FerretDB/wire => ../wire
require (
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778
github.com/AlekSi/pointer v1.2.0
github.com/FerretDB/wire v0.1.2
github.com/alecthomas/kong v1.11.0
github.com/arl/statsviz v0.6.0
github.com/FerretDB/wire v0.1.7
github.com/alecthomas/kong v1.12.1
github.com/arl/statsviz v0.7.2
github.com/google/uuid v1.6.0
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jackc/pgx/v5 v5.7.5
github.com/oapi-codegen/runtime v1.1.1
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
github.com/jackc/pgx/v5 v5.7.6
github.com/modelcontextprotocol/go-sdk v0.2.0
github.com/oapi-codegen/runtime v1.1.2
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.64.0
github.com/stretchr/testify v1.10.0
github.com/prometheus/common v0.67.2
github.com/stretchr/testify v1.11.1
github.com/xdg-go/scram v1.1.2
go.mongodb.org/mongo-driver/v2 v2.2.2
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0
go.opentelemetry.io/otel/sdk v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250605174209-3bf9d2afd4f0
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
go.mongodb.org/mongo-driver/v2 v2.4.0
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20251028130051-c0531f9c3451
golang.org/x/net v0.46.0
golang.org/x/sys v0.38.0
golang.org/x/term v0.36.0
)
require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+82 -76
View File
@@ -1,31 +1,33 @@
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778 h1:Hv1Z0ZnUWI1NYHXKUE16SWUcv+VyFARJHQ6dGkd2TIM=
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778/go.mod h1:S8uLHjY/2pJvG9qkWwDX1AVYYknPI92r2qh/f0uysr0=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/FerretDB/wire v0.1.2 h1:fL3beAgZ8cX+rKKirJ0k7GEdmjQq4e6n2iRlnGCF3m4=
github.com/FerretDB/wire v0.1.2/go.mod h1:bXVDsxwA8ugBIfpD/eLYjHjdC7U95ygaWVX7RW2UysY=
github.com/FerretDB/wire v0.1.7 h1:yh6tL0rU8ub+KuKqiqTlFJGpdfL4V7kg/8PBPDX0sBE=
github.com/FerretDB/wire v0.1.7/go.mod h1:KjV8URsECCZWwJBBzlTvyl9LEXvnE362tL4tg52VHXI=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.11.0 h1:y++1gI7jf8O7G7l4LZo5ASFhrhJvzc+WgF/arranEmM=
github.com/alecthomas/kong v1.11.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
github.com/arl/statsviz v0.7.2 h1:xnuIfRiXE4kvxEcfGL+IE3mKH1BXNHuE+eJELIh7oOA=
github.com/arl/statsviz v0.7.2/go.mod h1:XlrbiT7xYT03xaW9JMMfD8KFUhBOESJwfyNJu83PbB0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -36,20 +38,20 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
@@ -61,112 +63,116 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/modelcontextprotocol/go-sdk v0.2.0 h1:PESNYOmyM1c369tRkzXLY5hHrazj8x9CY1Xu0fLCryM=
github.com/modelcontextprotocol/go-sdk v0.2.0/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver/v2 v2.2.2 h1:9cYuS3fl1Xhqwpfazso10V7BHQD58kCgtzhfAmJYz9c=
go.mongodb.org/mongo-driver/v2 v2.2.2/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps=
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250605174209-3bf9d2afd4f0 h1:LAtxhSighcSwwB7zC5nvOl3BlSEKP2AtwGyJNnt1O0w=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250605174209-3bf9d2afd4f0/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto/x509roots/fallback v0.0.0-20251028130051-c0531f9c3451 h1:ntT/VrHMoSyusO9qDIYKxtkMfluoOkLrr6AJfVRROsE=
golang.org/x/crypto/x509roots/fallback v0.0.0-20251028130051-c0531f9c3451/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+1 -1
View File
@@ -107,7 +107,7 @@ linters-settings:
alias: otelsdkresource
- pkg: go.opentelemetry.io/otel/sdk/trace
alias: otelsdktrace
- pkg: go.opentelemetry.io/otel/semconv/v1.21.0
- pkg: go.opentelemetry.io/otel/semconv/v1.34.0
alias: otelsemconv
# ineffassign
lll:
+8 -6
View File
@@ -1619,12 +1619,14 @@ func TestAggregateCompatSort(t *testing.T) {
}}}},
},
"DotNotationIndex": {
pipeline: bson.A{bson.D{{"$sort", bson.D{
{"v.0", 1},
{"_id", 1}, // sort by _id when v is the same.
}}}},
},
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/355
// "DotNotationIndex": {
// pipeline: bson.A{bson.D{{"$sort", bson.D{
// {"v.0", 1},
// {"_id", 1}, // sort by _id when v is the same.
// }}}},
// },
"DotNotationNonExistent": {
pipeline: bson.A{bson.D{{"$sort", bson.D{
{"invalid.foo", 1},
+1
View File
@@ -1327,6 +1327,7 @@ func TestAggregateCommandCursor(t *testing.T) {
Name: "FailedToParse",
Message: "The 'cursor' option is required, except for aggregate with the explain argument",
},
altMessage: "The 'cursor' option is required, except for aggregate with explain",
},
"Empty": {
cursor: bson.D{},
+5 -5
View File
@@ -148,10 +148,10 @@ func TestCreateUserCommand(t *testing.T) {
},
"MissingPwdOrExternal": {
payload: bson.D{
{"createUser", "mising_pwd_or_external"},
{"createUser", "missing_pwd_or_external"},
{"roles", bson.A{}},
},
user: "mising_pwd_or_external",
user: "missing_pwd_or_external",
err: &mongo.CommandError{
Code: 2,
Name: "BadValue",
@@ -178,7 +178,7 @@ func TestCreateUserCommand(t *testing.T) {
{Key: "serverKey"},
}},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"SuccessWithSCRAMSHA256": {
payload: bson.D{
@@ -200,7 +200,7 @@ func TestCreateUserCommand(t *testing.T) {
{Key: "serverKey"},
}},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"WithComment": {
payload: bson.D{
@@ -222,7 +222,7 @@ func TestCreateUserCommand(t *testing.T) {
{Key: "serverKey"},
}},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"MissingRoles": {
payload: bson.D{
+2 -2
View File
@@ -57,14 +57,14 @@ func TestDropUserCommand(t *testing.T) {
Name: "UserNotFound",
Message: fmt.Sprintf("User 'not_found_user@%s' not found", db.Name()),
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/942",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5323",
},
"Success": {
username: "a_user",
expected: bson.D{
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
}
+3 -3
View File
@@ -146,7 +146,7 @@ func TestUpdateUserCommand(t *testing.T) {
}}},
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"PasswordChange": {
username: "a_user",
@@ -165,7 +165,7 @@ func TestUpdateUserCommand(t *testing.T) {
}}},
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"PasswordChangeWithSCRAMMechanism": {
username: "a_user_with_scram_mechanism",
@@ -241,7 +241,7 @@ func TestUpdateUserCommand(t *testing.T) {
}}},
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"InvalidRoles": {
username: "invalid_roles",
+3 -3
View File
@@ -205,7 +205,7 @@ func TestUsersInfoCommand(t *testing.T) {
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/961
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/962
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"WithSCRAMSHA256": {
dbName: dbNames.E,
@@ -234,7 +234,7 @@ func TestUsersInfoCommand(t *testing.T) {
}},
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"FromSameDatabase": {
dbName: dbNames.B,
@@ -467,7 +467,7 @@ func TestUsersInfoCommand(t *testing.T) {
}},
{"ok", float64(1)},
},
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
failsForFerretDB: "https://github.com/FerretDB/FerretDB/issues/5313",
},
"SingleDatabaseLong": {
dbName: "no_users",
+100 -97
View File
@@ -272,12 +272,12 @@ func TestSASLContinueErrors(t *testing.T) {
conv := client.NewConversation()
payload := must.NotFail(conv.Step(""))
msg := must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg := wire.MustOpMsg(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(payload)},
"$db", "admin",
)).Encode())))
)
var resBody wire.MsgBody
_, resBody, err = conn.Request(ctx, msg)
@@ -296,23 +296,23 @@ func TestSASLContinueErrors(t *testing.T) {
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"conversationId", int32(1),
"done", false,
"payload", wirebson.Binary{},
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
payload = "invalid"
msg = must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg = wire.MustOpMsg(
"saslContinue", int32(1),
"conversationId", int32(1),
"payload", wirebson.Binary{B: []byte(payload)},
"$db", "admin",
)).Encode())))
)
_, resBody, err = conn.Request(ctx, msg)
require.NoError(t, err)
@@ -322,21 +322,21 @@ func TestSASLContinueErrors(t *testing.T) {
FixCluster(t, res)
expected = must.NotFail(wirebson.NewDocument(
expected = wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Authentication failed.",
"code", int32(18),
"codeName", "AuthenticationFailed",
))
)
testutil.AssertEqual(t, expected, res)
msg = must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg = wire.MustOpMsg(
"saslContinue", int32(1),
"conversationId", int32(1),
"payload", wirebson.Binary{},
"$db", "admin",
)).Encode())))
)
_, resBody, err = conn.Request(ctx, msg)
require.NoError(t, err)
@@ -346,21 +346,21 @@ func TestSASLContinueErrors(t *testing.T) {
FixCluster(t, res)
expected = must.NotFail(wirebson.NewDocument(
expected = wirebson.MustDocument(
"ok", float64(0),
"errmsg", "No SASL session state found",
"code", int32(17),
"codeName", "ProtocolError",
))
)
testutil.AssertEqual(t, expected, res)
})
t.Run("NoAuthenticatedUser", func(t *testing.T) {
msg := must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg := wire.MustOpMsg(
"connectionStatus", int64(1),
"$db", testutil.DatabaseName(t),
)).Encode())))
)
var resBody wire.MsgBody
_, resBody, err = conn.Request(ctx, msg)
@@ -372,23 +372,23 @@ func TestSASLContinueErrors(t *testing.T) {
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
"authInfo", must.NotFail(wirebson.NewDocument(
"authenticatedUsers", wirebson.MakeArray(0),
"authenticatedUserRoles", wirebson.MakeArray(0),
)),
expected := wirebson.MustDocument(
"authInfo", wirebson.MustDocument(
"authenticatedUsers", wirebson.MustArray(),
"authenticatedUserRoles", wirebson.MustArray(),
),
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
})
t.Run("FindFails", func(t *testing.T) {
msg := must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg := wire.MustOpMsg(
"find", "values",
"filter", must.NotFail(wirebson.NewDocument()),
"filter", wirebson.MustDocument(),
"$db", testutil.DatabaseName(t),
)).Encode())))
)
var resBody wire.MsgBody
_, resBody, err = conn.Request(ctx, msg)
@@ -400,12 +400,12 @@ func TestSASLContinueErrors(t *testing.T) {
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Command find requires authentication",
"code", int32(13),
"codeName", "Unauthorized",
))
)
testutil.AssertEqual(t, expected, res)
})
@@ -420,12 +420,12 @@ func TestSASLContinueNoConversation(t *testing.T) {
ctx, conn := s.Ctx, s.WireConn
msg := must.NotFail(wire.NewOpMsg(must.NotFail(must.NotFail(wirebson.NewDocument(
msg := wire.MustOpMsg(
"saslContinue", int32(1),
"conversationId", int32(1),
"payload", wirebson.Binary{},
"$db", "admin",
)).Encode())))
)
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
@@ -435,12 +435,12 @@ func TestSASLContinueNoConversation(t *testing.T) {
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "No SASL session state found",
"code", int32(17),
"codeName", "ProtocolError",
))
)
testutil.AssertEqual(t, expected, res)
}
@@ -551,12 +551,12 @@ func TestHelloIsMasterOpQuerySpeculative(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"conversationId", int32(1),
"done", true,
"payload", serverPayload,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
@@ -585,12 +585,12 @@ func TestHelloIsMasterOpQuerySpeculative(t *testing.T) {
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "No SASL session state found",
"code", int32(17),
"codeName", "ProtocolError",
))
)
testutil.AssertEqual(t, expected, res)
})
})
@@ -729,28 +729,28 @@ func TestHelloSpeculative(t *testing.T) {
// as is request sent by legacy Mongo shell
msg := wire.MustOpMsg(
"hello", int32(1),
"speculativeAuthenticate", must.NotFail(wirebson.NewDocument(
"speculativeAuthenticate", wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(clientPayload)},
"db", "admin",
)),
),
"saslSupportedMechs", "admin."+username,
"client", must.NotFail(wirebson.NewDocument(
"application", must.NotFail(wirebson.NewDocument(
"client", wirebson.MustDocument(
"application", wirebson.MustDocument(
"name", "MongoDB Shell",
)),
"driver", must.NotFail(wirebson.NewDocument(
),
"driver", wirebson.MustDocument(
"name", "MongoDB Internal Client",
"version", "7.0.8",
)),
"os", must.NotFail(wirebson.NewDocument(
),
"os", wirebson.MustDocument(
"type", "Linux",
"name", "Ubuntu",
"architecture", "x86_64",
"version", "22.04",
)),
)),
),
),
"$db", "admin",
)
@@ -794,7 +794,7 @@ func TestHelloSpeculative(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"isWritablePrimary", true,
"maxBsonObjectSize", int32(16777216),
"maxMessageSizeBytes", int32(48000000),
@@ -806,13 +806,13 @@ func TestHelloSpeculative(t *testing.T) {
"maxWireVersion", int32(21),
"readOnly", false,
"saslSupportedMechs", saslSupportedMechs,
"speculativeAuthenticate", must.NotFail(wirebson.NewDocument(
"speculativeAuthenticate", wirebson.MustDocument(
"conversationId", int32(1),
"done", false,
"payload", serverPayload,
)),
),
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
@@ -833,7 +833,7 @@ func TestHelloSpeculative(t *testing.T) {
require.NoError(t, err)
var res *wirebson.Document
res, err = must.NotFail(resBody.(*wire.OpMsg).RawDocument()).Decode()
res, err = must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).Decode()
require.NoError(t, err)
serverPayload, ok := res.Get("payload").(wirebson.Binary)
@@ -841,12 +841,12 @@ func TestHelloSpeculative(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"conversationId", int32(1),
"done", false,
"payload", serverPayload,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
@@ -869,7 +869,7 @@ func TestHelloSpeculative(t *testing.T) {
require.NoError(t, err)
var res *wirebson.Document
res, err = must.NotFail(resBody.(*wire.OpMsg).RawDocument()).Decode()
res, err = must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).Decode()
require.NoError(t, err)
serverPayload, ok := res.Get("payload").(wirebson.Binary)
@@ -877,12 +877,12 @@ func TestHelloSpeculative(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"conversationId", int32(1),
"done", true,
"payload", serverPayload,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
})
@@ -900,17 +900,17 @@ func TestHelloSpeculative(t *testing.T) {
require.NoError(t, err)
var res *wirebson.Document
res, err = must.NotFail(resBody.(*wire.OpMsg).RawDocument()).Decode()
res, err = must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).Decode()
require.NoError(t, err)
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "No SASL session state found",
"code", int32(17),
"codeName", "ProtocolError",
))
)
testutil.AssertEqual(t, expected, res)
})
}
@@ -1000,7 +1000,7 @@ func TestHelloOpQuerySASLSupportedMechs(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"isWritablePrimary", true,
"maxBsonObjectSize", int32(16777216),
"maxMessageSizeBytes", int32(48000000),
@@ -1010,13 +1010,13 @@ func TestHelloOpQuerySASLSupportedMechs(t *testing.T) {
"maxWireVersion", int32(21),
"readOnly", false,
"saslSupportedMechs", saslSupportedMechs,
"speculativeAuthenticate", must.NotFail(wirebson.NewDocument(
"speculativeAuthenticate", wirebson.MustDocument(
"conversationId", int32(1),
"done", false,
"payload", payloadAny,
)),
),
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
}
@@ -1048,62 +1048,62 @@ func TestSASLStartOpQueryErrors(t *testing.T) {
failsForFerretDB string
}{
"WrongAuthDB": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(payload)},
)),
),
fullCollectionName: "wrong-auth-db.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Authentication failed.",
"code", int32(18),
"codeName", "AuthenticationFailed",
)),
),
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/932",
},
"NotAllowed$db": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(payload)},
"$db", "admin",
)),
),
fullCollectionName: "admin.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "$db is not allowed in OP_QUERY requests",
"code", int32(40621),
"codeName", "Location40621",
)),
),
},
"InvalidPayload": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte("invalid")},
)),
),
fullCollectionName: "admin.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Authentication failed.",
"code", int32(18),
"codeName", "AuthenticationFailed",
)),
),
},
"UnsupportedMechanism": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "UNKNOWN-MECHANISM",
"payload", wirebson.Binary{B: []byte(payload)},
)),
),
fullCollectionName: "admin.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Received authentication for mechanism UNKNOWN-MECHANISM which is not enabled",
"code", int32(334),
"codeName", "MechanismUnavailable",
)),
),
},
} {
t.Run(name, func(tt *testing.T) {
@@ -1156,45 +1156,45 @@ func TestSASLStartErrors(t *testing.T) {
res *wirebson.Document
}{
"InvalidOptions": {
msg: must.NotFail(wirebson.NewDocument(
msg: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(payload)},
"options", "invalid",
"$db", "admin",
)),
res: must.NotFail(wirebson.NewDocument(
),
res: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'saslStart.options' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
)),
),
},
"InvalidOptionsSkipEmptyExchange": {
msg: must.NotFail(wirebson.NewDocument(
msg: wirebson.MustDocument(
"saslStart", int32(1),
"mechanism", "SCRAM-SHA-256",
"payload", wirebson.Binary{B: []byte(payload)},
"options", wirebson.MustDocument("skipEmptyExchange", "invalid"),
"$db", "admin",
)),
res: must.NotFail(wirebson.NewDocument(
),
res: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Authentication failed.",
"code", int32(18),
"codeName", "AuthenticationFailed",
)),
),
},
} {
t.Run(name, func(t *testing.T) {
q := must.NotFail(wire.NewOpMsg(must.NotFail(tc.msg.Encode())))
q := must.NotFail(wire.NewOpMsg(tc.msg))
var resBody wire.MsgBody
_, resBody, err = conn.Request(ctx, q)
require.NoError(t, err)
var res *wirebson.Document
res, err = must.NotFail(resBody.(*wire.OpMsg).RawDocument()).Decode()
res, err = must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).Decode()
require.NoError(t, err)
FixCluster(t, res)
@@ -1218,33 +1218,33 @@ func TestSASLContinueOpQueryErrors(t *testing.T) {
reply *wirebson.Document
}{
"AuthenticationError": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslContinue", int32(1),
"conversationId", int32(1),
"payload", wirebson.Binary{},
)),
),
fullCollectionName: "wrong-auth-db.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Authentication failed.",
"code", int32(18),
"codeName", "AuthenticationFailed",
)),
),
},
"NotAllowed$db": {
query: must.NotFail(wirebson.NewDocument(
query: wirebson.MustDocument(
"saslContinue", int32(1),
"conversationId", int32(1),
"payload", wirebson.Binary{},
"$db", "admin",
)),
),
fullCollectionName: "admin.$cmd",
reply: must.NotFail(wirebson.NewDocument(
reply: wirebson.MustDocument(
"ok", float64(0),
"errmsg", "$db is not allowed in OP_QUERY requests",
"code", int32(40621),
"codeName", "Location40621",
)),
),
},
} {
t.Run(name, func(t *testing.T) {
@@ -1266,7 +1266,10 @@ func TestSASLContinueOpQueryErrors(t *testing.T) {
q.FullCollectionName = "admin.$cmd"
q.NumberToReturn = -1
conn, err := wireclient.Connect(ctx, s.MongoDBURI, testutil.Logger(t))
cleanUri, _, _, _, err := wireclient.Credentials(s.MongoDBURI)
require.NoError(t, err)
conn, err := wireclient.Connect(ctx, cleanUri, testutil.Logger(t))
require.NoError(t, err)
_, resBody, err := conn.Request(ctx, q)
@@ -1280,12 +1283,12 @@ func TestSASLContinueOpQueryErrors(t *testing.T) {
err = res.Replace("payload", wirebson.Binary{})
require.NoError(t, err)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"conversationId", int32(1),
"done", false,
"payload", wirebson.Binary{},
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
+2 -2
View File
@@ -108,7 +108,7 @@ func TestInsertFind(t *testing.T) {
func TestFindOtelComment(t *testing.T) {
ctx, collection := setup.Setup(t, shareddata.Scalars)
ctx, span := otel.Tracer("").Start(ctx, "TestOtelComment")
ctx, span := otel.Tracer("").Start(ctx, "TestFindOtelComment")
defer span.End()
comment, err := observability.CommentFromSpanContext(span.SpanContext())
@@ -580,7 +580,7 @@ func TestDebugCommandErrors(t *testing.T) {
}
AssertMatchesCommandError(t, expected, res.Err())
msg := `(InternalError) [msg_ferretdebugerror.go:67 handler.(*Handler).msgFerretDebugError] lazy error`
msg := `(InternalError) msg_ferretdebugerror.go:59 (handler.(*Handler).msgFerretDebugError): lazy error`
assert.EqualError(t, res.Err(), msg)
var doc bson.D
+14 -14
View File
@@ -269,48 +269,48 @@ func TestHostInfoCommand(t *testing.T) {
for _, elem := range a {
switch elem.Key {
case "system":
var elemForComparsion bson.D
var elemForComparison bson.D
for _, subElem := range elem.Value.(bson.D) {
switch subElem.Key {
case "currentTime":
assert.IsType(t, primitive.DateTime(0), subElem.Value)
elemForComparsion = append(elemForComparsion, bson.E{"currentTime", primitive.DateTime(0)})
elemForComparison = append(elemForComparison, bson.E{"currentTime", primitive.DateTime(0)})
case "hostname", "cpuArch":
assert.IsType(t, "", subElem.Value)
elemForComparsion = append(elemForComparsion, bson.E{subElem.Key, ""})
elemForComparison = append(elemForComparison, bson.E{subElem.Key, ""})
case "cpuAddrSize", "numCores":
assert.IsType(t, int32(0), subElem.Value)
elemForComparsion = append(elemForComparsion, bson.E{subElem.Key, int32(0)})
elemForComparison = append(elemForComparison, bson.E{subElem.Key, int32(0)})
case "numPhysicalCores", "numCpuSockets", "numNumaNodes", "numaEnabled", "memSizeMB", "memLimitMB":
// not implemented in FerretDB, do nothing
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/587
default:
elemForComparsion = append(elemForComparsion, subElem)
elemForComparison = append(elemForComparison, subElem)
}
}
actualComparable = append(actualComparable, bson.E{"system", elemForComparsion})
actualComparable = append(actualComparable, bson.E{"system", elemForComparison})
case "os":
var elemForComparsion bson.D
var elemForComparison bson.D
for _, subElem := range elem.Value.(bson.D) {
switch subElem.Key {
case "type", "name", "version":
assert.IsType(t, "", subElem.Value)
elemForComparsion = append(elemForComparsion, bson.E{subElem.Key, ""})
elemForComparison = append(elemForComparison, bson.E{subElem.Key, ""})
default:
elemForComparsion = append(elemForComparsion, subElem)
elemForComparison = append(elemForComparison, subElem)
}
}
actualComparable = append(actualComparable, bson.E{"os", elemForComparsion})
actualComparable = append(actualComparable, bson.E{"os", elemForComparison})
case "extra":
assert.IsType(t, bson.D{}, elem.Value)
@@ -349,7 +349,7 @@ func TestListCommandsCommand(t *testing.T) {
err := collection.Database().RunCommand(ctx, bson.D{{"listCommands", 42}}).Decode(&res)
require.NoError(t, err)
var actualForComparsion bson.D
var actualForComparison bson.D
for _, v := range res {
switch v.Key {
@@ -383,10 +383,10 @@ func TestListCommandsCommand(t *testing.T) {
}
}
actualForComparsion = append(actualForComparsion, bson.E{"commands", commandsComparable})
actualForComparison = append(actualForComparison, bson.E{"commands", commandsComparable})
default:
actualForComparsion = append(actualForComparsion, v)
actualForComparison = append(actualForComparison, v)
}
}
@@ -395,7 +395,7 @@ func TestListCommandsCommand(t *testing.T) {
{"ok", float64(1)},
}
AssertEqualDocuments(t, expected, actualForComparsion)
AssertEqualDocuments(t, expected, actualForComparison)
}
func TestValidateCommand(t *testing.T) {
@@ -17,8 +17,9 @@ const _CompatTestCaseResultType_name = "NonEmptyResultEmptyResult"
var _CompatTestCaseResultType_index = [...]uint8{0, 14, 25}
func (i CompatTestCaseResultType) String() string {
if i < 0 || i >= CompatTestCaseResultType(len(_CompatTestCaseResultType_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_CompatTestCaseResultType_index)-1 {
return "CompatTestCaseResultType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _CompatTestCaseResultType_name[_CompatTestCaseResultType_index[i]:_CompatTestCaseResultType_index[i+1]]
return _CompatTestCaseResultType_name[_CompatTestCaseResultType_index[idx]:_CompatTestCaseResultType_index[idx+1]]
}
+3
View File
@@ -170,6 +170,9 @@ func TestCurrentOpGetMore(tt *testing.T) {
}
func TestCurrentOpExplain(tt *testing.T) {
// TODO https://github.com/FerretDB/FerretDB/issues/4871
tt.Skip("https://github.com/FerretDB/FerretDB/issues/4871")
tt.Parallel()
s := setup.SetupWithOpts(tt, new(setup.SetupOpts))
+3 -4
View File
@@ -249,15 +249,14 @@ func TestFindCommandSingleBatch(t *testing.T) {
func TestFindCommandExhausted(tt *testing.T) {
tt.Parallel()
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
s := setup.SetupWithOpts(tt, &setup.SetupOpts{PoolSize: 1})
collection, ctx := s.Collection, s.Ctx
arr := integration.GenerateDocuments(1, 3)
_, err := collection.InsertMany(ctx, arr)
require.NoError(t, err)
require.NoError(tt, err)
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
var res bson.D
err = collection.Database().RunCommand(ctx, bson.D{
+22 -14
View File
@@ -28,6 +28,8 @@ import (
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
"github.com/FerretDB/FerretDB/v2/integration"
"github.com/FerretDB/FerretDB/v2/integration/setup"
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
@@ -310,8 +312,8 @@ func TestGetMoreCommand(t *testing.T) {
// Do not run subtests in t.Parallel() to eliminate the occurrence
// of session error.
// Supporting session would help us understand fix it
// https://github.com/FerretDB/FerretDB/issues/153.
// Supporting session would help us understand fix it.
// TODO https://github.com/FerretDB/FerretDB/issues/153
//
// > Location50738
// > Cannot run getMore on cursor 2053655655200551971,
@@ -467,6 +469,11 @@ func TestGetMoreCommandBatchSize(t *testing.T) {
require.Equal(t, i-1, cursor.RemainingBatchLength())
}
if setup.IsYugabyteDB(t) {
// TODO https://github.com/yugabyte/yugabyte-db/issues/27989
t.Skip("https://github.com/yugabyte/yugabyte-db/issues/27989")
}
// get rest of documents from the cursor to ensure cursor is exhausted
var res bson.D
err = cursor.All(ctx, &res)
@@ -563,8 +570,8 @@ func TestGetMoreCommandConnection(t *testing.T) {
t.Run("SameClient", func(t *testing.T) {
// Do not run subtests in t.Parallel() to eliminate the occurrence
// of session error.
// Supporting session would help us understand fix it
// https://github.com/FerretDB/FerretDB/issues/153.
// Supporting session would help us understand fix it.
// TODO https://github.com/FerretDB/FerretDB/issues/153
//
// > Location50738
// > Cannot run getMore on cursor 2053655655200551971,
@@ -826,16 +833,15 @@ func TestGetMoreCommandMaxTimeMSErrors(t *testing.T) {
func TestGetMoreCommandExhausted(tt *testing.T) {
s := setup.SetupWithOpts(tt, nil)
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
collection := s.Collection
db, ctx := collection.Database(), s.Ctx
arr := integration.GenerateDocuments(0, 10)
_, err := collection.InsertMany(ctx, arr)
require.NoError(t, err)
require.NoError(tt, err)
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
var res bson.D
err = db.RunCommand(ctx, bson.D{
@@ -1130,10 +1136,11 @@ func TestFindCommandFirstBatchMaxTimeMS(t *testing.T) {
}
t.Run("GetMore", func(tt *testing.T) {
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
for i := 0; i < 2; i++ {
time.Sleep(100 * time.Millisecond)
ctxutil.Sleep(ctx, 100*time.Millisecond)
var res bson.D
err = collection.Database().RunCommand(ctx, getMoreCmd).Decode(&res)
require.NoError(t, err)
@@ -1153,9 +1160,10 @@ func TestFindCommandFirstBatchMaxTimeMS(t *testing.T) {
})
t.Run("GetMoreEmpty", func(tt *testing.T) {
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
ctxutil.Sleep(ctx, 100*time.Millisecond)
time.Sleep(100 * time.Millisecond)
var res bson.D
err = collection.Database().RunCommand(ctx, getMoreCmd).Decode(&res)
require.NoError(t, err)
@@ -1225,7 +1233,7 @@ func TestGetMoreCommandAfterInsertion(t *testing.T) {
}
t.Run("GetMore", func(tt *testing.T) {
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
for i := 0; i < 2; i++ {
var res bson.D
@@ -1247,7 +1255,7 @@ func TestGetMoreCommandAfterInsertion(t *testing.T) {
})
t.Run("GetMoreEmpty", func(tt *testing.T) {
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/270")
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/5445")
var res bson.D
err = collection.Database().RunCommand(ctx, getMoreCmd).Decode(&res)
+3 -2
View File
@@ -39,9 +39,10 @@ type explainCompatTestCase struct {
// testExplainCompatError tests explain compatibility test cases.
// This test does not work for successful aggregate pipeline tests,
// due to compat requiring cursor option.
// If you see following error, use `testAggregateStagesCompat` test instead.
// If you see one of following errors, use `testAggregateStagesCompat` test instead.
//
// `(FailedToParse) The 'cursor' option is required, except for aggregate with the explain argument`
// `(FailedToParse) The 'cursor' option is required, except for aggregate with the explain argument`.
// `(FailedToParse) The 'cursor' option is required, except for aggregate with explain`.
func testExplainCompatError(tt *testing.T, testCases map[string]explainCompatTestCase) {
tt.Helper()
+42 -34
View File
@@ -1,68 +1,76 @@
module github.com/FerretDB/FerretDB/v2/integration
go 1.24
go 1.25
toolchain go1.24.4
toolchain go1.25.4
replace github.com/FerretDB/FerretDB/v2 => ../
// replace github.com/FerretDB/wire => ../../wire
require (
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778
github.com/AlekSi/pointer v1.2.0
github.com/FerretDB/FerretDB/v2 v2.0.0-00010101000000-000000000000
github.com/FerretDB/wire v0.1.2
github.com/FerretDB/wire v0.1.7
github.com/FerretDB/xfail v0.1.0
github.com/google/uuid v1.6.0
github.com/prometheus/client_golang v1.22.0
github.com/stretchr/testify v1.10.0
github.com/prometheus/client_golang v1.23.2
github.com/stretchr/testify v1.11.1
github.com/xdg-go/scram v1.1.2
go.mongodb.org/mongo-driver v1.17.4
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.61.0
go.opentelemetry.io/otel v1.36.0
go.mongodb.org/mongo-driver v1.17.6
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.63.0
go.opentelemetry.io/otel v1.38.0
)
require (
github.com/arl/statsviz v0.6.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/arl/statsviz v0.7.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/modelcontextprotocol/go-sdk v0.2.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/common v0.67.2 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver/v2 v2.2.2 // indirect
go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+88 -70
View File
@@ -1,23 +1,29 @@
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778 h1:Hv1Z0ZnUWI1NYHXKUE16SWUcv+VyFARJHQ6dGkd2TIM=
github.com/AlekSi/lazyerrors v0.0.0-20251102120325-67e66cc79778/go.mod h1:S8uLHjY/2pJvG9qkWwDX1AVYYknPI92r2qh/f0uysr0=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/FerretDB/wire v0.1.2 h1:fL3beAgZ8cX+rKKirJ0k7GEdmjQq4e6n2iRlnGCF3m4=
github.com/FerretDB/wire v0.1.2/go.mod h1:bXVDsxwA8ugBIfpD/eLYjHjdC7U95ygaWVX7RW2UysY=
github.com/FerretDB/wire v0.1.7 h1:yh6tL0rU8ub+KuKqiqTlFJGpdfL4V7kg/8PBPDX0sBE=
github.com/FerretDB/wire v0.1.7/go.mod h1:KjV8URsECCZWwJBBzlTvyl9LEXvnE362tL4tg52VHXI=
github.com/FerretDB/xfail v0.1.0 h1:NmwdtwCmQ12X4VutLL+XilG/vbLRbG6RUVQabnI1JiQ=
github.com/FerretDB/xfail v0.1.0/go.mod h1:u34yzRVZdvuc/fjegLYi+FztscWts9pP9ZUJ7wY9OyE=
github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arl/statsviz v0.7.2 h1:xnuIfRiXE4kvxEcfGL+IE3mKH1BXNHuE+eJELIh7oOA=
github.com/arl/statsviz v0.7.2/go.mod h1:XlrbiT7xYT03xaW9JMMfD8KFUhBOESJwfyNJu83PbB0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -28,20 +34,21 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -50,107 +57,118 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/modelcontextprotocol/go-sdk v0.2.0 h1:PESNYOmyM1c369tRkzXLY5hHrazj8x9CY1Xu0fLCryM=
github.com/modelcontextprotocol/go-sdk v0.2.0/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver/v2 v2.2.2 h1:9cYuS3fl1Xhqwpfazso10V7BHQD58kCgtzhfAmJYz9c=
go.mongodb.org/mongo-driver/v2 v2.2.2/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.61.0 h1:60BQjL3MUzaYUT8uHfpAFSEe3JOiBT+p19fA/CDOEak=
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.61.0/go.mod h1:FaTsrpewmN1Je1UyUtkYU1YqHuhhzE2bRySP668ImSM=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.63.0 h1:6IOE2J+3fFJKJ/8riwf6XrazdEr261L8TEY6T0uSjEM=
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.63.0/go.mod h1:kbPDiVJGSE06bBx6sJlDMXFQ15/gnY4MA1ppkso9LYE=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+2 -2
View File
@@ -475,7 +475,7 @@ func TestCreateIndexesCompatUnique(t *testing.T) {
insertDoc: bson.D{{"v", "value"}},
new: true,
// This test case passes only because of our hack for
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1147
// TODO https://github.com/documentdb/documentdb/issues/25
// failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/296",
},
"NotExistingFieldIndex": {
@@ -515,7 +515,7 @@ func TestCreateIndexesCompatUnique(t *testing.T) {
},
insertDoc: bson.D{{"v", int32(42)}},
// This test case passes only because of our hack for
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1147
// TODO https://github.com/documentdb/documentdb/issues/25
// failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/296",
},
} {
+14 -15
View File
@@ -23,7 +23,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/FerretDB/FerretDB/v2/internal/util/must"
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
"github.com/FerretDB/FerretDB/v2/integration/setup"
@@ -46,7 +45,7 @@ func TestOpQuery(t *testing.T) {
_, resBody, err := conn.Request(ctx, q)
require.NoError(t, err)
resMsg, err := resBody.(*wire.OpReply).RawDocument().Decode()
resMsg, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
ok := resMsg.Get("ok")
@@ -68,7 +67,7 @@ func TestOpQuery(t *testing.T) {
_, resBody, err := conn.Request(ctx, q)
require.NoError(t, err)
resMsg, err := resBody.(*wire.OpReply).RawDocument().Decode()
resMsg, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
ok := resMsg.Get("ok")
@@ -90,17 +89,17 @@ func TestOpQuery(t *testing.T) {
_, resBody, err := conn.Request(ctx, q)
require.NoError(t, err)
res, err := resBody.(*wire.OpReply).RawDocument().Decode()
res, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Bad numberToReturn (0) for $cmd type ns - can only be 1 or -1",
"code", int32(16979),
"codeName", "Location16979",
))
)
testutil.AssertEqual(t, expected, res)
})
@@ -132,7 +131,7 @@ func TestOpQueryIsMaster(t *testing.T) {
require.NoError(t, err)
assert.NotZero(t, resHeader.RequestID)
res, err := resBody.(*wire.OpReply).RawDocument().Decode()
res, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
connectionID := res.Get("connectionId")
@@ -156,7 +155,7 @@ func TestOpQueryIsMaster(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"ismaster", true,
"maxBsonObjectSize", int32(16777216),
"maxMessageSizeBytes", int32(48000000),
@@ -166,7 +165,7 @@ func TestOpQueryIsMaster(t *testing.T) {
"maxWireVersion", int32(21),
"readOnly", false,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
})
}
@@ -201,7 +200,7 @@ func TestOpQueryIsMasterHelloOk(t *testing.T) {
require.NoError(t, err)
assert.NotZero(t, resHeader.RequestID)
res, err := resBody.(*wire.OpReply).RawDocument().Decode()
res, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
connectionID := res.Get("connectionId")
@@ -225,7 +224,7 @@ func TestOpQueryIsMasterHelloOk(t *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"helloOk", true,
"ismaster", true,
"maxBsonObjectSize", int32(16777216),
@@ -236,7 +235,7 @@ func TestOpQueryIsMasterHelloOk(t *testing.T) {
"maxWireVersion", int32(21),
"readOnly", false,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
})
}
@@ -262,7 +261,7 @@ func TestOpQueryHello(tt *testing.T) {
_, resBody, err := conn.Request(ctx, q)
require.NoError(t, err)
res, err := resBody.(*wire.OpReply).RawDocument().Decode()
res, err := resBody.(*wire.OpReply).DocumentRaw().Decode()
require.NoError(t, err)
connectionID := res.Get("connectionId")
@@ -286,7 +285,7 @@ func TestOpQueryHello(tt *testing.T) {
FixCluster(t, res)
expectedComparable := must.NotFail(wirebson.NewDocument(
expectedComparable := wirebson.MustDocument(
"isWritablePrimary", true,
"maxBsonObjectSize", int32(16777216),
"maxMessageSizeBytes", int32(48000000),
@@ -296,6 +295,6 @@ func TestOpQueryHello(tt *testing.T) {
"maxWireVersion", int32(21),
"readOnly", false,
"ok", float64(1),
))
)
testutil.AssertEqual(t, expectedComparable, res)
}
+60 -9
View File
@@ -29,7 +29,9 @@ import (
func TestInsertZeroTimestamp(tt *testing.T) {
tt.Parallel()
s := setup.SetupWithOpts(tt, &setup.SetupOpts{WireConn: setup.WireConnAuth})
var t testing.TB = tt
s := setup.SetupWithOpts(t, &setup.SetupOpts{WireConn: setup.WireConnAuth})
_, resp, err := s.WireConn.Request(s.Ctx, wire.MustOpMsg(
"insert", s.Collection.Name(),
@@ -44,32 +46,81 @@ func TestInsertZeroTimestamp(tt *testing.T) {
),
"$db", s.Collection.Database().Name(),
))
require.NoError(tt, err)
require.NoError(t, err)
actual, err := resp.(*wire.OpMsg).Document()
require.NoError(tt, err)
require.Equal(tt, 1.0, actual.Get("ok"))
require.NoError(t, err)
require.Equal(t, 1.0, actual.Get("ok"))
_, resp, err = s.WireConn.Request(s.Ctx, wire.MustOpMsg(
"find", s.Collection.Name(),
"$db", s.Collection.Database().Name(),
))
require.NoError(tt, err)
require.NoError(t, err)
actual, err = resp.(*wire.OpMsg).DocumentDeep()
require.NoError(tt, err)
require.Equal(tt, 1.0, actual.Get("ok"))
require.NoError(t, err)
require.Equal(t, 1.0, actual.Get("ok"))
batch := actual.Get("cursor").(*wirebson.Document).Get("firstBatch").(*wirebson.Array)
d := batch.Get(0).(*wirebson.Document).Get("d").(*wirebson.Document)
assert.Equal(tt, wirebson.Timestamp(0), d.Get("dv"))
assert.Equal(t, wirebson.Timestamp(0), d.Get("dv"))
// TODO https://github.com/FerretDB/FerretDB/issues/1608
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB/issues/1608")
t = setup.FailsForFerretDB(t, "https://github.com/FerretDB/FerretDB/issues/1608")
v := batch.Get(0).(*wirebson.Document).Get("v").(wirebson.Timestamp)
assert.NotEqual(t, wirebson.Timestamp(0), v)
assert.NotZero(t, v.I())
assert.InDelta(t, time.Now().Unix(), v.T(), 5.0)
}
func TestInsertZeroTimestampBypass(tt *testing.T) {
tt.Parallel()
var t testing.TB = tt
s := setup.SetupWithOpts(t, &setup.SetupOpts{WireConn: setup.WireConnAuth})
// TODO https://github.com/FerretDB/FerretDB/issues/5073
t = setup.FailsForMongoDB(t, "https://github.com/FerretDB/FerretDB/issues/5073")
_, resp, err := s.WireConn.Request(s.Ctx, wire.MustOpMsg(
"insert", s.Collection.Name(),
"documents", wirebson.MustArray(
wirebson.MustDocument(
"_id", "id",
"v", wirebson.Timestamp(0),
"d", wirebson.MustDocument(
"dv", wirebson.Timestamp(0),
),
),
),
"bypassEmptyTsReplacement", true,
"$db", s.Collection.Database().Name(),
))
require.NoError(t, err)
actual, err := resp.(*wire.OpMsg).Document()
require.NoError(t, err)
require.Equal(t, 1.0, actual.Get("ok"))
_, resp, err = s.WireConn.Request(s.Ctx, wire.MustOpMsg(
"find", s.Collection.Name(),
"$db", s.Collection.Database().Name(),
))
require.NoError(t, err)
actual, err = resp.(*wire.OpMsg).DocumentDeep()
require.NoError(t, err)
require.Equal(t, 1.0, actual.Get("ok"))
batch := actual.Get("cursor").(*wirebson.Document).Get("firstBatch").(*wirebson.Array)
d := batch.Get(0).(*wirebson.Document).Get("d").(*wirebson.Document)
assert.Equal(t, wirebson.Timestamp(0), d.Get("dv"))
v := batch.Get(0).(*wirebson.Document).Get("v").(wirebson.Timestamp)
assert.Equal(t, wirebson.Timestamp(0), v)
}
+2 -2
View File
@@ -116,10 +116,10 @@ func TestQueryArrayCompatDotNotation(t *testing.T) {
"FieldArrayIndex": {
filter: bson.D{{"v.foo[0]", int32(42)}},
},
"FieldArrayAsterix": {
"FieldArrayAsterisk": {
filter: bson.D{{"v.foo[*]", int32(42)}},
},
"FieldAsterix": {
"FieldAsterisk": {
filter: bson.D{{"v.*", int32(42)}},
},
"FieldAt": {
+9 -6
View File
@@ -125,14 +125,14 @@ func testQueryCompatWithProviders(t *testing.T, providers shareddata.Providers,
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
}
targetIdx, tagetErr := targetCollection.Indexes().CreateOne(ctx, mongo.IndexModel{
targetIdx, targetErr := targetCollection.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{"v", 1}},
})
compatIdx, compatErr := compatCollection.Indexes().CreateOne(ctx, mongo.IndexModel{
Keys: bson.D{{"v", 1}},
})
require.NoError(t, tagetErr)
require.NoError(t, targetErr)
require.NoError(t, compatErr)
require.Equal(t, compatIdx, targetIdx)
@@ -283,10 +283,13 @@ func TestQueryCompatSort(t *testing.T) {
resultType: EmptyResult,
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/241",
},
"DotNotationIndex": {
filter: bson.D{},
sort: bson.D{{"v.0", 1}, {"_id", 1}},
},
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/355
// "DotNotationIndex": {
// filter: bson.D{},
// sort: bson.D{{"v.0", 1}, {"_id", 1}},
// },
"DotNotationNonExistent": {
filter: bson.D{},
sort: bson.D{{"invalid.foo", 1}, {"_id", 1}},
+5
View File
@@ -680,6 +680,11 @@ func TestQueryShowRecordID(t *testing.T) {
cursor, err := tc.collection.Find(ctx, bson.D{}, opts)
require.NoError(t, err)
if setup.IsYugabyteDB(t) {
// TODO https://github.com/yugabyte/yugabyte-db/issues/27989
t.Skip("https://github.com/yugabyte/yugabyte-db/issues/27989")
}
var res []bson.D
err = cursor.All(ctx, &res)
require.NoError(t, cursor.Close(ctx))
+10 -11
View File
@@ -130,12 +130,12 @@ func TestEndSessionsImmediateCleanup(t *testing.T) {
time.Sleep(10 * cleanupInterval)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, conn, dbName, cName, sessionID, cursorID, expectedErr)
}
@@ -152,12 +152,12 @@ func TestEndSessionsErrors(t *testing.T) {
t.Run("NotArray", func(t *testing.T) {
sessions := "invalid"
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'endSessions.endSessions' is the wrong type 'string', expected type 'array'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
endSessions(t, ctx, conn, dbName, sessions, expectedErr)
})
@@ -179,17 +179,17 @@ func TestEndSessionsUnauthenticated(t *testing.T) {
))
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Command endSessions requires authentication",
"code", int32(13),
"codeName", "Unauthorized",
))
)
testutil.AssertEqual(t, expected, res)
}
@@ -205,7 +205,7 @@ func endSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNam
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -216,9 +216,8 @@ func endSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNam
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
+31 -31
View File
@@ -79,12 +79,12 @@ func TestKillAllSessions(t *testing.T) {
killAllSessions(t, ctx, user1Conn, dbName, users, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr)
@@ -107,21 +107,21 @@ func TestKillAllSessions(t *testing.T) {
killAllSessions(t, ctx, user1Conn, dbName, users, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user2CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user2SessionID, user2CursorID, expectedErr2)
})
@@ -139,21 +139,21 @@ func TestKillAllSessions(t *testing.T) {
killAllSessions(t, ctx, user1Conn, dbName, users, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID1),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID1, cursorID1, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID2),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID2, cursorID2, expectedErr2)
})
@@ -173,12 +173,12 @@ func TestKillAllSessions(t *testing.T) {
killSessions(t, ctx, user1Conn, dbName, sessions, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -222,12 +222,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("NotArrayUsers", func(t *testing.T) {
users := "invalid"
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions' is the wrong type 'string', expected type 'array'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -235,12 +235,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("NotDocumentUser", func(t *testing.T) {
users := wirebson.MustArray("invalid")
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions.0' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -248,12 +248,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("MissingDB", func(t *testing.T) {
users := wirebson.MustArray(wirebson.MustDocument("user", "foo"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions.db' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -261,12 +261,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("BoolDB", func(t *testing.T) {
users := wirebson.MustArray(wirebson.MustDocument("db", true, "user", "foo"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions.db' is the wrong type 'bool', expected type 'string'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -274,12 +274,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("MissingUser", func(t *testing.T) {
users := wirebson.MustArray(wirebson.MustDocument("db", "bar"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions.user' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -287,12 +287,12 @@ func TestKillAllSessionsErrors(t *testing.T) {
t.Run("BoolUser", func(t *testing.T) {
users := wirebson.MustArray(wirebson.MustDocument("db", "bar", "user", false))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsCmd.killAllSessions.user' is the wrong type 'bool', expected type 'string'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessions(t, ctx, conn, dbName, users, expectedErr)
})
@@ -341,21 +341,21 @@ func TestKillAllSessionsAllUsers(t *testing.T) {
killAllSessions(t, ctx, user1Conn, dbName, users, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user2CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user2SessionID, user2CursorID, expectedErr2)
}
@@ -371,7 +371,7 @@ func killAllSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, d
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -382,9 +382,9 @@ func killAllSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, d
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
@@ -17,6 +17,7 @@ package session
import (
"context"
"fmt"
"net/url"
"testing"
"github.com/FerretDB/wire"
@@ -84,12 +85,12 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -110,12 +111,12 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user2Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -163,20 +164,20 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID1),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, session1ID, cursorID1, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID2),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, session2ID, cursorID2, expectedErr2)
})
@@ -197,12 +198,12 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, user1CursorID, expectedErr)
getMore(t, ctx, user2Conn, dbName, cName, sessionID, user2CursorID, nil)
@@ -223,20 +224,20 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID1),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID1, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID2),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID2, expectedErr2)
})
@@ -251,12 +252,12 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -272,12 +273,12 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user2Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -329,20 +330,20 @@ func TestKillAllSessionsByPattern(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, nil)
expectedErr1 := must.NotFail(wirebson.NewDocument(
expectedErr1 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr1)
expectedErr2 := must.NotFail(wirebson.NewDocument(
expectedErr2 := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user2CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user2SessionID, user2CursorID, expectedErr2)
getMore(t, ctx, user1Conn, dbName, cName, user1NotKillSessionID, user1NotKillCursorID, nil)
@@ -387,12 +388,12 @@ func TestKillAllSessionsByPatternAllUsers(t *testing.T) {
killAllSessionsByPattern(t, ctx, user2Conn, dbName, pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -407,12 +408,12 @@ func TestKillAllSessionsByPatternAllUsers(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, emptyUsersPattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -430,12 +431,12 @@ func TestKillAllSessionsByPatternAllUsers(t *testing.T) {
// user1 does not match the pattern, but the cursor of user1 is deleted unexpectedly
// and FerretDB exhibits the same behavior for the compatibility
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr)
})
@@ -451,12 +452,12 @@ func TestKillAllSessionsByPatternAllUsers(t *testing.T) {
killAllSessionsByPattern(t, ctx, user1Conn, dbName, user1Pattern, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -474,12 +475,12 @@ func TestKillAllSessionsByPatternAllUsers(t *testing.T) {
// sessions of user2 was killed, not user1, but the cursor of user1 is deleted unexpectedly
// and FerretDB exhibits the same behavior for the compatibility
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user1CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, expectedErr)
})
}
@@ -504,12 +505,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
t.Run("NonArrayPatterns", func(t *testing.T) {
pattern := "invalid"
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern' is the wrong type 'string', expected type 'array'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -517,12 +518,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
t.Run("StringPattern", func(t *testing.T) {
pattern := wirebson.MustArray("invalid")
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.0' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -532,12 +533,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
pattern := wirebson.MustArray(wirebson.MustDocument("invalid", "foo"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.invalid' is an unknown field.",
"code", int32(40415),
"codeName", "Location40415",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -545,12 +546,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
t.Run("LsidString", func(t *testing.T) {
pattern := wirebson.MustArray(wirebson.MustDocument("lsid", "invalid"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -558,12 +559,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
t.Run("LsidMissingID", func(t *testing.T) {
pattern := wirebson.MustArray(wirebson.MustDocument("lsid", wirebson.MustDocument()))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.id' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -571,12 +572,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
t.Run("LsidStringID", func(t *testing.T) {
pattern := wirebson.MustArray(wirebson.MustDocument("lsid", wirebson.MustDocument("id", "invalid")))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.id' is the wrong type 'string', expected type 'binData'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -586,12 +587,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"id", wirebson.Binary{Subtype: wirebson.BinaryFunction},
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.id' is the wrong binData type 'function', expected type 'UUID'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -601,12 +602,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"id", wirebson.Binary{Subtype: wirebson.BinaryUUID},
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "uuid must be a 16-byte binary field with UUID (4) subtype",
"code", int32(207),
"codeName", "InvalidUUID",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -618,12 +619,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"id", wirebson.Binary{B: randomUUID[:], Subtype: wirebson.BinaryUUID},
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.uid' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -636,12 +637,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"uid", "invalid",
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.uid' is the wrong type 'string', expected type 'binData'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -654,12 +655,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"uid", wirebson.Binary{},
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Unsupported SHA256Block hash length: 0",
"code", int32(12),
"codeName", "UnsupportedFormat",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -672,12 +673,12 @@ func TestKillAllSessionsByPatternErrors(t *testing.T) {
"uid", wirebson.Binary{Subtype: wirebson.BinaryFunction},
)))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.lsid.uid' is the wrong binData type 'function', expected type 'general'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -705,12 +706,12 @@ func TestKillAllSessionsByPatternUIDErrors(t *testing.T) {
wirebson.MustDocument("uid", "invalid"),
)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.uid' is the wrong type 'string', expected type 'binData'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -720,12 +721,12 @@ func TestKillAllSessionsByPatternUIDErrors(t *testing.T) {
wirebson.MustDocument("uid", wirebson.Binary{}),
)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "Unsupported SHA256Block hash length: 0",
"code", int32(12),
"codeName", "UnsupportedFormat",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -735,12 +736,12 @@ func TestKillAllSessionsByPatternUIDErrors(t *testing.T) {
wirebson.MustDocument("uid", wirebson.Binary{Subtype: wirebson.BinaryFunction}),
)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.uid' is the wrong binData type 'function', expected type 'general'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -766,12 +767,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
t.Run("NotArrayUsers", func(t *testing.T) {
pattern := wirebson.MustArray(wirebson.MustDocument("users", "invalid"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users' is the wrong type 'string', expected type 'array'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -781,12 +782,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
"users", wirebson.MustArray("invalid"),
))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users.0' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -796,12 +797,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
"users", wirebson.MustArray(wirebson.MustDocument("user", "foo")),
))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users.db' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -811,12 +812,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
"users", wirebson.MustArray(wirebson.MustDocument("db", true, "user", "foo")),
))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users.db' is the wrong type 'bool', expected type 'string'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -826,12 +827,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
"users", wirebson.MustArray(wirebson.MustDocument("db", "bar")),
))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users.user' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -841,12 +842,12 @@ func TestKillAllSessionsByPatternUsersErrors(t *testing.T) {
"users", wirebson.MustArray(wirebson.MustDocument("db", "bar", "user", false)),
))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'KillAllSessionsByPatternCmd.killAllSessionsByPattern.users.user' is the wrong type 'bool', expected type 'string'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killAllSessionsByPattern(t, ctx, user1Conn, dbName, pattern, expectedErr)
})
@@ -890,14 +891,17 @@ func createKillSessionUser(t *testing.T, ctx context.Context, db *mongo.Database
}).Err()
require.NoError(t, err)
conn, err := wireclient.Connect(ctx, mongoDBURI, testutil.Logger(t))
clearUri, _, _, authMechanism, err := wireclient.Credentials(mongoDBURI)
require.NoError(t, err)
conn, err := wireclient.Connect(ctx, clearUri, testutil.Logger(t))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn.Close())
})
err = conn.Login(ctx, username, password, db.Name())
err = conn.Login(ctx, url.UserPassword(username, password), db.Name(), authMechanism)
require.NoError(t, err)
return conn
@@ -914,7 +918,7 @@ func killAllSessionsByPattern(t testing.TB, ctx context.Context, conn *wireclien
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -925,9 +929,9 @@ func killAllSessionsByPattern(t testing.TB, ctx context.Context, conn *wireclien
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
+13 -13
View File
@@ -121,12 +121,12 @@ func TestKillSessions(t *testing.T) {
killSessions(t, ctx, conn, dbName, sessions, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -142,12 +142,12 @@ func TestKillSessions(t *testing.T) {
refreshSessions(t, ctx, conn, dbName, nil, sessions, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -163,12 +163,12 @@ func TestKillSessions(t *testing.T) {
endSessions(t, ctx, conn, dbName, sessions, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, conn, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -176,12 +176,12 @@ func TestKillSessions(t *testing.T) {
t.Run("EmptyUUIDSessionID", func(t *testing.T) {
sessions := wirebson.MustArray(wirebson.MustDocument("id", wirebson.Binary{Subtype: wirebson.BinaryUUID}))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "uuid must be a 16-byte binary field with UUID (4) subtype",
"code", int32(207),
"codeName", "InvalidUUID",
))
)
killSessions(t, ctx, conn, dbName, sessions, expectedErr)
})
@@ -245,12 +245,12 @@ func TestKillSessionsDifferentUser(t *testing.T) {
getMore(t, ctx, user1Conn, dbName, cName, user1SessionID, user1CursorID, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", user2CursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, user2Conn, dbName, cName, user2SessionID, user2CursorID, expectedErr)
})
}
@@ -266,7 +266,7 @@ func killSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNa
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -277,9 +277,9 @@ func killSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNa
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
+17 -17
View File
@@ -87,12 +87,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("NotArray", func(t *testing.T) {
sessions := "invalid"
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'refreshSessions.refreshSessions' is the wrong type 'string', expected type 'array'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -100,12 +100,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("NotSessionDocument", func(t *testing.T) {
sessions := wirebson.MustArray("invalid")
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'refreshSessions.refreshSessionsFromClient.0' is the wrong type 'string', expected type 'object'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -113,12 +113,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("MissingID", func(t *testing.T) {
sessions := wirebson.MustArray(wirebson.MustDocument())
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'refreshSessions.refreshSessionsFromClient.id' is missing but a required field",
"code", int32(40414),
"codeName", "Location40414",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -126,12 +126,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("WrongIDType", func(t *testing.T) {
sessions := wirebson.MustArray(wirebson.MustDocument("id", "invalid"))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'refreshSessions.refreshSessionsFromClient.id' is the wrong type 'string', expected type 'binData'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -139,12 +139,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("WrongIDSubtype", func(t *testing.T) {
sessions := wirebson.MustArray(wirebson.MustDocument("id", wirebson.Binary{Subtype: wirebson.BinaryFunction}))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'refreshSessions.refreshSessionsFromClient.id' is the wrong binData type 'function', expected type 'UUID'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -152,12 +152,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
t.Run("NotUUID", func(t *testing.T) {
sessions := wirebson.MustArray(wirebson.MustDocument("id", wirebson.Binary{Subtype: wirebson.BinaryUUID}))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "uuid must be a 16-byte binary field with UUID (4) subtype",
"code", int32(207),
"codeName", "InvalidUUID",
))
)
refreshSessions(t, ctx, conn, dbName, nil, sessions, expectedErr)
})
@@ -166,12 +166,12 @@ func TestRefreshSessionsErrors(t *testing.T) {
lsid := wirebson.Binary{Subtype: wirebson.BinaryUUID}
sessions := wirebson.MustArray(wirebson.MustDocument("id", wirebson.Binary{Subtype: wirebson.BinaryUUID}))
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "uuid must be a 16-byte binary field with UUID (4) subtype",
"code", int32(207),
"codeName", "InvalidUUID",
))
)
refreshSessions(t, ctx, conn, dbName, lsid, sessions, expectedErr)
})
@@ -196,7 +196,7 @@ func refreshSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, d
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -207,9 +207,9 @@ func refreshSessions(t testing.TB, ctx context.Context, conn *wireclient.Conn, d
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
+38 -26
View File
@@ -19,6 +19,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"net/url"
"testing"
"github.com/FerretDB/wire"
@@ -43,14 +44,17 @@ func TestSessionConnection(t *testing.T) {
ctx, collection, db, conn1 := s.Ctx, s.Collection, s.Collection.Database(), s.WireConn
cName, dbName := collection.Name(), db.Name()
conn2, err := wireclient.Connect(ctx, s.MongoDBURI, testutil.Logger(t))
clearUri, _, _, authMechanism, err := wireclient.Credentials(s.MongoDBURI)
require.NoError(t, err)
conn2, err := wireclient.Connect(ctx, clearUri, testutil.Logger(t))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn2.Close())
})
err = conn2.Login(ctx, "username", "password", "admin")
err = conn2.Login(ctx, url.UserPassword("username", "password"), "admin", authMechanism)
require.NoError(t, err)
_, err = collection.InsertMany(ctx, bson.A{
@@ -122,10 +126,15 @@ func TestSessionConnection(t *testing.T) {
t.Run("CloseConnection", func(t *testing.T) {
var anotherConn *wireclient.Conn
anotherConn, err = wireclient.Connect(ctx, s.MongoDBURI, testutil.Logger(t))
var creds *url.Userinfo
clearUri, creds, _, authMechanism, err = wireclient.Credentials(s.MongoDBURI)
require.NoError(t, err)
err = anotherConn.Login(ctx, "username", "password", "admin")
anotherConn, err = wireclient.Connect(ctx, clearUri, testutil.Logger(t))
require.NoError(t, err)
err = anotherConn.Login(ctx, creds, "admin", authMechanism)
require.NoError(t, err)
sessionID := startSession(t, ctx, anotherConn)
@@ -185,12 +194,12 @@ func TestSessionConnection(t *testing.T) {
killCursors(t, ctx, conn1, dbName, cName, cursorID, sessionID, nil)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", fmt.Sprintf("cursor id %d not found", cursorID),
"code", int32(43),
"codeName", "CursorNotFound",
))
)
getMore(t, ctx, conn1, dbName, cName, sessionID, cursorID, expectedErr)
})
@@ -202,12 +211,12 @@ func TestSessionConnection(t *testing.T) {
invalidSessionID := "invalid"
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
"errmsg", "BSON field 'OperationSessionInfo.lsid.id' is the wrong type 'string', expected type 'binData'",
"code", int32(14),
"codeName", "TypeMismatch",
))
)
killCursors(t, ctx, conn1, dbName, cName, cursorID, invalidSessionID, expectedErr)
@@ -235,7 +244,7 @@ func TestFindLsidErrors(t *testing.T) {
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -260,7 +269,7 @@ func TestFindLsidErrors(t *testing.T) {
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -285,7 +294,7 @@ func TestFindLsidErrors(t *testing.T) {
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -310,7 +319,7 @@ func TestFindLsidErrors(t *testing.T) {
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -335,7 +344,7 @@ func TestFindLsidErrors(t *testing.T) {
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -380,14 +389,17 @@ func TestSessionConnectionDifferentUser(t *testing.T) {
}).Err()
require.NoError(t, err)
userConn, err := wireclient.Connect(ctx, s.MongoDBURI, testutil.Logger(t))
clearUri, _, _, authMechanism, err := wireclient.Credentials(s.MongoDBURI)
require.NoError(t, err)
userConn, err := wireclient.Connect(ctx, clearUri, testutil.Logger(t))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, userConn.Close())
})
err = userConn.Login(ctx, user, pass, dbName)
err = userConn.Login(ctx, url.UserPassword(user, pass), "admin", authMechanism)
require.NoError(t, err)
t.Cleanup(func() {
@@ -484,14 +496,14 @@ func TestSessionConnectionDifferentUser(t *testing.T) {
cursorID := find(t, ctx, adminConn, dbName, cName, sessionID)
expectedErr := must.NotFail(wirebson.NewDocument(
expectedErr := wirebson.MustDocument(
"ok", float64(0),
// errmsg field is not compared, because as it is difficult produce exact format of document as below
// `not authorized on admin to execute command{ killCursors: "test", cursors: [ 8541858944752455730 ],
// lsid: { id: UUID("363dad0b-d9b8-406f-9575-b11a3779faa0") }, $db: "admin" }`
"code", int32(13),
"codeName", "Unauthorized",
))
)
killCursors(t, ctx, userConn, dbName, cName, cursorID, sessionID, expectedErr)
@@ -510,7 +522,7 @@ func startSession(t testing.TB, ctx context.Context, conn *wireclient.Conn) wire
require.NoError(t, err)
var res *wirebson.Document
res, err = must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err = must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -553,7 +565,7 @@ func killCursors(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNam
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -569,13 +581,13 @@ func killCursors(t testing.TB, ctx context.Context, conn *wireclient.Conn, dbNam
return
}
expected := must.NotFail(wirebson.NewDocument(
expected := wirebson.MustDocument(
"cursorsKilled", wirebson.MustArray(cursorID),
"cursorsNotFound", wirebson.MakeArray(0),
"cursorsAlive", wirebson.MakeArray(0),
"cursorsUnknown", wirebson.MakeArray(0),
"cursorsNotFound", wirebson.MustArray(),
"cursorsAlive", wirebson.MustArray(),
"cursorsUnknown", wirebson.MustArray(),
"ok", float64(1),
))
)
testutil.AssertEqual(t, expected, res)
}
@@ -602,7 +614,7 @@ func find(t testing.TB, ctx context.Context, conn *wireclient.Conn, db, coll str
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
@@ -665,7 +677,7 @@ func getMore(t testing.TB, ctx context.Context, conn *wireclient.Conn, db, coll
_, resBody, err := conn.Request(ctx, msg)
require.NoError(t, err)
res, err := must.NotFail(resBody.(*wire.OpMsg).RawDocument()).DecodeDeep()
res, err := must.NotFail(resBody.(*wire.OpMsg).DocumentRaw()).DecodeDeep()
require.NoError(t, err)
integration.FixCluster(t, res)
+3 -3
View File
@@ -22,6 +22,7 @@ import (
"strings"
"testing"
"github.com/AlekSi/lazyerrors"
"github.com/FerretDB/wire/wireclient"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/mongo"
@@ -29,7 +30,6 @@ import (
"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
"go.opentelemetry.io/otel"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
)
@@ -95,7 +95,7 @@ func makeClient(ctx context.Context, uri string, disableOtel bool) (*mongo.Clien
func setupClient(tb testing.TB, ctx context.Context, uri string, disableOtel bool) *mongo.Client {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "setupClient")
ctx, span := otel.Tracer("").Start(ctx, "setup.setupClient")
defer span.End()
u, err := url.Parse(uri)
@@ -137,7 +137,7 @@ func setupClient(tb testing.TB, ctx context.Context, uri string, disableOtel boo
func setupWireConn(tb testing.TB, ctx context.Context, uri string, l *slog.Logger) *wireclient.Conn {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "setupWireConn")
ctx, span := otel.Tracer("").Start(ctx, "setup.setupWireConn")
defer span.End()
conn, err := wireclient.Connect(ctx, uri, l)
+9 -13
View File
@@ -17,8 +17,6 @@ package setup
import (
"flag"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -38,6 +36,15 @@ func IsMongoDB(tb testing.TB) bool {
return *targetBackendF == "mongodb"
}
// IsYugabyteDB returns true if the current test is running for `ferretdb-yugabytedb` backend.
//
// This function should not be used lightly.
func IsYugabyteDB(tb testing.TB) bool {
tb.Helper()
return *targetBackendF == "ferretdb-yugabytedb"
}
// ensureIssueURL panics if URL is not a valid FerretDB issue URL.
func ensureIssueURL(url string) {
ferretDB := strings.HasPrefix(url, "https://github.com/FerretDB/FerretDB/issues/")
@@ -98,17 +105,6 @@ func SkipForMongoDB(tb testing.TB, reason string) {
}
}
// Dir returns the absolute directory of this package.
func Dir(tb testing.TB) string {
tb.Helper()
_, file, _, ok := runtime.Caller(0)
require.True(tb, ok)
require.True(tb, filepath.IsAbs(file))
return filepath.Dir(file)
}
// Main is the entry point for all integration test packages.
// It should be called from main_test.go in each package.
func Main(m *testing.M) {
+38 -41
View File
@@ -19,18 +19,16 @@ import (
"log/slog"
"net/url"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"github.com/FerretDB/FerretDB/v2/internal/clientconn"
"github.com/FerretDB/FerretDB/v2/internal/documentdb"
"github.com/FerretDB/FerretDB/v2/internal/handler"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
"github.com/FerretDB/FerretDB/v2/internal/util/setup"
"github.com/FerretDB/FerretDB/v2/internal/util/state"
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
)
// ListenerOpts represents setup options for in-process FerretDB listener.
@@ -85,13 +83,13 @@ func listenerMongoDBURI(tb testing.TB, hostPort, unixSocketPath string) string {
func setupListener(tb testing.TB, ctx context.Context, opts *ListenerOpts, logger *slog.Logger) string {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "setupListener")
ctx, span := otel.Tracer("").Start(ctx, "setup.setupListener")
defer span.End()
require.Empty(tb, *targetURLF, "-target-url must be empty for in-process FerretDB")
switch *targetBackendF {
case "ferretdb":
case "ferretdb", "ferretdb-yugabytedb":
require.NotEmpty(tb, *postgreSQLURLF, "-postgresql-url must be set for %q", *targetBackendF)
case "mongodb":
@@ -109,59 +107,58 @@ func setupListener(tb testing.TB, ctx context.Context, opts *ListenerOpts, logge
opts = new(ListenerOpts)
}
p, err := documentdb.NewPool(*postgreSQLURLF, logging.WithName(logger, "pool"), sp)
require.NoError(tb, err)
handlerOpts := &handler.NewOpts{
Pool: p,
Auth: true,
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/566
TCPHost: "",
ReplSetName: "",
L: logging.WithName(logger, "handler"),
ConnMetrics: listenerMetrics.ConnMetrics,
//exhaustruct:enforce
wireOpts := &setup.SetupOpts{
Logger: logger,
StateProvider: sp,
Metrics: middleware.NewMetrics(),
PostgreSQLURL: *postgreSQLURLF,
Auth: true,
ReplSetName: "", // TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/566
SessionCleanupInterval: opts.SessionCleanupInterval,
}
h, err := handler.New(handlerOpts)
require.NoError(tb, err)
ProxyAddr: "",
ProxyTLSCertFile: "",
ProxyTLSKeyFile: "",
ProxyTLSCAFile: "",
listenerOpts := clientconn.ListenerOpts{
Handler: h,
Metrics: listenerMetrics,
Logger: logger,
Mode: clientconn.NormalMode,
ProxyAddr: *targetProxyAddrF,
TestRecordsDir: filepath.Join(Dir(tb), "..", "..", "tmp", "records"),
}
TCPAddr: "",
UnixAddr: "",
TLSAddr: "",
TLSCertFile: "",
TLSKeyFile: "",
TLSCAFile: "",
Mode: middleware.NormalMode,
TestRecordsDir: testutil.TmpRecordsDir,
if *targetProxyAddrF != "" {
listenerOpts.Mode = clientconn.DiffNormalMode
DataAPIAddr: "",
}
switch {
case *targetUnixSocketF:
listenerOpts.Unix = unixSocketPath(tb)
wireOpts.UnixAddr = unixSocketPath(tb)
default:
listenerOpts.TCP = "127.0.0.1:0"
wireOpts.TCPAddr = "127.0.0.1:0"
}
l, err := clientconn.Listen(&listenerOpts)
require.NoError(tb, err)
if *targetProxyAddrF != "" {
wireOpts.Mode = middleware.DiffNormalMode
wireOpts.ProxyAddr = *targetProxyAddrF
}
res := setup.Setup(ctx, wireOpts)
require.NotNil(tb, res)
runDone := make(chan struct{})
go func() {
defer close(runDone)
runCtx, runSpan := otel.Tracer("").Start(ctx, "setupListener.Run")
runCtx, runSpan := otel.Tracer("").Start(ctx, "setup.setupListener.Run")
defer runSpan.End()
l.Run(runCtx)
res.Run(runCtx)
}()
// ensure that all listener's and handler's logs are written before test ends
@@ -173,9 +170,9 @@ func setupListener(tb testing.TB, ctx context.Context, opts *ListenerOpts, logge
switch {
case *targetUnixSocketF:
unixSocketPath = l.UnixAddr().String()
unixSocketPath = res.WireListener.UnixAddr().String()
default:
hostPort = l.TCPAddr().String()
hostPort = res.WireListener.TCPAddr().String()
}
uri := listenerMongoDBURI(tb, hostPort, unixSocketPath)
+61 -17
View File
@@ -41,7 +41,7 @@ import (
// Flags.
var (
targetURLF = flag.String("target-url", "", "target system's URL; if empty, in-process FerretDB is used")
targetBackendF = flag.String("target-backend", "", "target system's backend: '%s'"+strings.Join(allBackends, "', '"))
targetBackendF = flag.String("target-backend", "", "target system's backend: "+strings.Join(allBackends, ", "))
postgreSQLURLF = flag.String("postgresql-url", "", "in-process FerretDB: PostgreSQL URL")
targetUnixSocketF = flag.Bool("target-unix-socket", false, "in-process FerretDB: use Unix domain socket")
@@ -62,7 +62,7 @@ var (
// Other globals.
var (
allBackends = []string{"ferretdb", "mongodb"}
allBackends = []string{"ferretdb", "ferretdb-yugabytedb", "mongodb"}
)
// WireConn defines if and how wire client connection is established.
@@ -147,7 +147,7 @@ func SetupWithOpts(tb testing.TB, opts *SetupOpts) *SetupResult {
ctx, cancel := context.WithCancel(testutil.Ctx(tb))
setupCtx, span := otel.Tracer("").Start(ctx, "SetupWithOpts")
setupCtx, span := otel.Tracer("").Start(ctx, "setup.SetupWithOpts")
defer span.End()
if opts == nil {
@@ -204,7 +204,10 @@ func SetupWithOpts(tb testing.TB, opts *SetupOpts) *SetupResult {
var conn *wireclient.Conn
if opts.WireConn != WireConnNoConn {
conn = setupWireConn(tb, setupCtx, uri, testutil.Logger(tb))
clearUri, creds, _, authMechanism, err := wireclient.Credentials(uri)
require.NoError(tb, err)
conn = setupWireConn(tb, setupCtx, clearUri, testutil.Logger(tb))
if opts.WireConn == WireConnAuth {
u, err := url.Parse(uri)
@@ -216,7 +219,7 @@ func SetupWithOpts(tb testing.TB, opts *SetupOpts) *SetupResult {
pass, _ := u.User.Password()
require.NotEmpty(tb, pass)
require.NoError(tb, conn.Login(ctx, user, pass, "admin"))
require.NoError(tb, conn.Login(ctx, creds, "admin", authMechanism))
}
}
@@ -245,7 +248,7 @@ func Setup(tb testing.TB, providers ...shareddata.Provider) (context.Context, *m
func setupCollection(tb testing.TB, ctx context.Context, client *mongo.Client, opts *SetupOpts) *mongo.Collection {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "setupCollection")
ctx, span := otel.Tracer("").Start(ctx, "setup.setupCollection")
defer span.End()
var ownDatabase bool
@@ -261,10 +264,10 @@ func setupCollection(tb testing.TB, ctx context.Context, client *mongo.Client, o
collection := database.Collection(collectionName)
// drop remnants of the previous failed run
_ = collection.Drop(ctx)
if ownDatabase {
_ = database.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}})
_ = database.Drop(ctx)
cleanupDatabase(tb, ctx, database)
} else {
cleanupCollection(tb, ctx, collection)
}
// drop collection and (possibly) database unless test failed
@@ -274,16 +277,13 @@ func setupCollection(tb testing.TB, ctx context.Context, client *mongo.Client, o
return
}
err := collection.Drop(ctx)
require.NoError(tb, err)
if ownDatabase {
err = database.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}}).Err()
require.NoError(tb, err)
cleanupDatabase(tb, ctx, database)
err = database.Drop(ctx)
require.NoError(tb, err)
return
}
cleanupCollection(tb, ctx, collection)
})
var inserted bool
@@ -309,7 +309,7 @@ func setupCollection(tb testing.TB, ctx context.Context, client *mongo.Client, o
func InsertProviders(tb testing.TB, ctx context.Context, collection *mongo.Collection, providers ...shareddata.Provider) (inserted bool) {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "InsertProviders")
ctx, span := otel.Tracer("").Start(ctx, "setup.InsertProviders")
defer span.End()
for _, provider := range providers {
@@ -342,3 +342,47 @@ func insertBenchmarkProvider(tb testing.TB, ctx context.Context, collection *mon
return
}
// cleanupCollection deletes all documents in the collection for ferretdb-yugabytedb backend
// or drops the collection for other backends.
func cleanupCollection(tb testing.TB, ctx context.Context, collection *mongo.Collection) {
tb.Helper()
if IsYugabyteDB(tb) {
// dropping collection or database fails for ferretdb-yugabytedb
// TODO https://github.com/yugabyte/yugabyte-db/issues/27698
_, err := collection.DeleteMany(ctx, bson.D{})
require.NoError(tb, err)
return
}
err := collection.Drop(ctx)
require.NoError(tb, err)
}
// cleanupDatabase drops all users, then deletes all collections in the database
// for ferretdb-yugabytedb backend or drops the database for other backends.
func cleanupDatabase(tb testing.TB, ctx context.Context, database *mongo.Database) {
tb.Helper()
err := database.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}}).Err()
require.NoError(tb, err)
if IsYugabyteDB(tb) {
// dropping collection or database fails for ferretdb-yugabytedb
// TODO https://github.com/yugabyte/yugabyte-db/issues/27698
var collections []string
collections, err = database.ListCollectionNames(ctx, bson.D{})
require.NoError(tb, err)
for _, collection := range collections {
cleanupCollection(tb, ctx, database.Collection(collection))
}
return
}
err = database.Drop(ctx)
require.NoError(tb, err)
}
+4 -10
View File
@@ -21,7 +21,6 @@ import (
"testing"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.opentelemetry.io/otel"
@@ -66,7 +65,7 @@ func SetupCompatWithOpts(tb testing.TB, opts *SetupCompatOpts) *SetupCompatResul
ctx, cancel := context.WithCancel(testutil.Ctx(tb))
setupCtx, span := otel.Tracer("").Start(ctx, "SetupCompatWithOpts")
setupCtx, span := otel.Tracer("").Start(ctx, "setup.SetupCompatWithOpts")
defer span.End()
if opts == nil {
@@ -130,14 +129,13 @@ func SetupCompat(tb testing.TB) (context.Context, []*mongo.Collection, []*mongo.
func setupCompatCollections(tb testing.TB, ctx context.Context, client *mongo.Client, opts *SetupCompatOpts, backend string) []*mongo.Collection {
tb.Helper()
ctx, span := otel.Tracer("").Start(ctx, "setupCompatCollections")
ctx, span := otel.Tracer("").Start(ctx, "setup.setupCompatCollections")
defer span.End()
database := client.Database(opts.databaseName)
// drop remnants of the previous failed run
_ = database.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}})
_ = database.Drop(ctx)
cleanupDatabase(tb, ctx, database)
// drop database unless test failed
tb.Cleanup(func() {
@@ -145,11 +143,7 @@ func setupCompatCollections(tb testing.TB, ctx context.Context, client *mongo.Cl
return
}
err := database.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}}).Err()
require.NoError(tb, err)
err = database.Drop(ctx)
require.NoError(tb, err)
cleanupDatabase(tb, ctx, database)
})
providers := slices.Clone(opts.Providers)
+2 -8
View File
@@ -27,7 +27,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
"go.mongodb.org/mongo-driver/mongo"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/v2/internal/util/debug"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
@@ -35,9 +34,6 @@ import (
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
)
// listenerMetrics are shared between tests.
var listenerMetrics = connmetrics.NewListenerMetrics()
// shutdown cancels context passed to startup components.
var shutdown context.CancelFunc
@@ -63,8 +59,6 @@ func Startup() {
*debugSetupF = true
}
prometheus.DefaultRegisterer.MustRegister(listenerMetrics)
ctx, shutdown = context.WithCancel(ctx)
startupWG.Add(1)
@@ -82,7 +76,7 @@ func Startup() {
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create debug handler", logging.Error(err))
}
h.Serve(ctx)
h.Run(ctx)
}()
if *otelTracesURLF != "" {
@@ -93,8 +87,8 @@ func Startup() {
ot, err := observability.NewOTelTraceExporter(&observability.OTelTraceExporterOpts{
Logger: logging.WithName(l, "otel"),
Service: "integration-tests",
URL: *otelTracesURLF,
Service: "integration-tests",
})
if err != nil {
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create Otel tracer", logging.Error(err))
+1 -1
View File
@@ -1446,7 +1446,7 @@ func TestUpdateFieldCompatSet(t *testing.T) {
doublesProvider := []shareddata.Provider{shareddata.Doubles}
testCases := map[string]updateCompatTestCase{
"SetNullInExisingField": {
"SetNullInExistingField": {
update: bson.D{{"$set", bson.D{{"v", nil}}}},
},
"DuplicateKeys": {
+35 -510
View File
@@ -29,110 +29,24 @@ import (
"net/netip"
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/AlekSi/lazyerrors"
"github.com/FerretDB/wire"
"github.com/FerretDB/wire/wirebson"
"github.com/pmezard/go-difflib/difflib"
"go.opentelemetry.io/otel"
otelattribute "go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
oteltrace "go.opentelemetry.io/otel/trace"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/conninfo"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/v2/internal/handler"
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
"github.com/FerretDB/FerretDB/v2/internal/handler/proxy"
"github.com/FerretDB/FerretDB/v2/internal/mongoerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/must"
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
)
// Mode represents FerretDB mode of operation.
type Mode string
const (
// NormalMode only handles requests.
NormalMode Mode = "normal"
// ProxyMode only proxies requests to another wire protocol compatible service.
ProxyMode Mode = "proxy"
// DiffNormalMode both handles requests and proxies them, then logs the diff.
// Only the FerretDB response is sent to the client.
DiffNormalMode Mode = "diff-normal"
// DiffProxyMode both handles requests and proxies them, then logs the diff.
// Only the proxy response is sent to the client.
DiffProxyMode Mode = "diff-proxy"
)
// AllModes includes all operation modes, with the first one being the default.
var AllModes = []string{
string(NormalMode),
string(ProxyMode),
string(DiffNormalMode),
string(DiffProxyMode),
}
// conn represents client connection.
type conn struct {
netConn net.Conn
mode Mode
l *slog.Logger
h *handler.Handler
m *connmetrics.ConnMetrics
proxy *proxy.Handler
lastRequestID atomic.Int32
m *middleware.Middleware
testRecordsDir string // if empty, no records are created
}
// newConnOpts represents newConn options.
type newConnOpts struct {
netConn net.Conn
mode Mode
l *slog.Logger
handler *handler.Handler
connMetrics *connmetrics.ConnMetrics
proxyAddr string
proxyTLSCertFile string
proxyTLSKeyFile string
proxyTLSCAFile string
testRecordsDir string // if empty, no records are created
}
// newConn creates a new client connection for given net.Conn.
func newConn(opts *newConnOpts) (*conn, error) {
if opts.mode == "" {
panic("mode required")
}
if opts.handler == nil {
panic("handler required")
}
var p *proxy.Handler
if opts.mode != NormalMode {
var err error
if p, err = proxy.New(opts.proxyAddr, opts.proxyTLSCertFile, opts.proxyTLSKeyFile, opts.proxyTLSCAFile); err != nil {
return nil, lazyerrors.Error(err)
}
}
return &conn{
netConn: opts.netConn,
mode: opts.mode,
l: opts.l,
h: opts.handler,
m: opts.connMetrics,
proxy: p,
testRecordsDir: opts.testRecordsDir,
}, nil
}
// run runs the client connection until ctx is canceled, client disconnects,
// or fatal error or panic is encountered.
//
@@ -140,54 +54,32 @@ func newConn(opts *newConnOpts) (*conn, error) {
//
// The caller is responsible for closing the underlying net.Conn.
func (c *conn) run(ctx context.Context) (err error) {
ctx, cancel := context.WithCancelCause(ctx)
defer func() {
cancel(lazyerrors.Errorf("run exits: %w", err))
}()
connInfo := conninfo.New()
defer connInfo.Close()
if c.netConn.RemoteAddr().Network() != "unix" {
connInfo.Peer, err = netip.ParseAddrPort(c.netConn.RemoteAddr().String())
if err != nil {
return
}
}
ctx = conninfo.Ctx(ctx, connInfo)
// That's not the best – it makes proxy handler very different from the main handler.
// Instead, proxy handler should map connections based on connInfo.
// TODO https://github.com/FerretDB/FerretDB/issues/4965
if c.proxy != nil {
go c.proxy.Run(ctx)
}
done := make(chan struct{})
// handle ctx cancellation
go func() {
select {
case <-done:
// nothing, let goroutine exit
case <-ctx.Done():
// unblocks ReadMessage below; any non-zero past value will do
if e := c.netConn.SetDeadline(time.Unix(0, 0)); e != nil {
c.l.WarnContext(ctx, fmt.Sprintf("Failed to set deadline: %s", e))
}
}
}()
ci := conninfo.New()
ctx, cancel := context.WithCancelCause(conninfo.Ctx(ctx, ci))
defer func() {
if p := recover(); p != nil {
c.l.LogAttrs(ctx, logging.LevelDPanic, fmt.Sprint(p), logging.Error(err))
err = errors.New("panic")
err = lazyerrors.New("panic")
}
// let goroutine above exit
close(done)
cancel(lazyerrors.Errorf("run exits: %w", err))
ci.Close()
}()
if c.netConn.RemoteAddr().Network() != "unix" {
ci.Peer, err = netip.ParseAddrPort(c.netConn.RemoteAddr().String())
if err != nil {
err = lazyerrors.Error(err)
return
}
}
go func() {
<-ctx.Done()
// unblocks ReadMessage in the processRequest below; any non-zero past value will do
_ = c.netConn.SetDeadline(time.Unix(0, 0))
}()
bufr := bufio.NewReader(c.netConn)
@@ -218,323 +110,43 @@ func (c *conn) run(ctx context.Context) (err error) {
bufw := bufio.NewWriter(c.netConn)
defer func() {
if e := bufw.Flush(); err == nil {
err = e
}
// c.netConn is closed by the caller
}()
for {
if err = c.processMessage(ctx, bufr, bufw); err != nil {
if err = c.processRequest(ctx, bufr, bufw); err != nil {
return
}
}
}
// processMessage reads the request, routes the request based on the operation mode
// and writes the response.
// processRequest reads the request, passes it to the middleware, and writes the response.
//
// Any error returned indicates the connection should be closed.
func (c *conn) processMessage(ctx context.Context, bufr *bufio.Reader, bufw *bufio.Writer) error {
func (c *conn) processRequest(ctx context.Context, bufr *bufio.Reader, bufw *bufio.Writer) error {
reqHeader, reqBody, err := wire.ReadMessage(bufr)
if err != nil {
return err
return lazyerrors.Error(err)
}
if c.l.Enabled(ctx, slog.LevelDebug) {
c.l.DebugContext(ctx, "Request header: "+reqHeader.String())
c.l.DebugContext(ctx, "Request message:\n"+reqBody.StringIndent())
req, err := middleware.RequestWire(reqHeader, reqBody)
if err != nil {
return lazyerrors.Error(err)
}
// diffLogLevel provides the level of logging for the diff between the "normal" and "proxy" responses.
// It is set to the highest level of logging used to log response.
diffLogLevel := slog.LevelDebug
// send request to proxy first (unless we are in normal mode)
// because FerretDB's handling could modify reqBody's documents,
// creating a data race
var proxyHeader *wire.MsgHeader
var proxyBody wire.MsgBody
if c.mode != NormalMode {
if c.proxy == nil {
panic("proxy addr was nil")
}
// TODO https://github.com/FerretDB/FerretDB/issues/1997
var resp *middleware.Response
resp, err = c.proxy.Handle(ctx, middleware.RequestWire(reqHeader, reqBody))
must.NoError(err)
proxyHeader = resp.WireHeader()
switch {
case resp.OpMsg != nil:
proxyBody = resp.OpMsg
case resp.OpReply != nil:
proxyBody = resp.OpReply
default:
panic("response body is nil")
}
resp := c.m.Handle(ctx, req)
if resp == nil {
return lazyerrors.New("middleware returned nil response")
}
// handle request unless we are in proxy mode
var resCloseConn bool
var resHeader *wire.MsgHeader
var resBody wire.MsgBody
if c.mode != ProxyMode {
resHeader, resBody, resCloseConn = c.route(ctx, reqHeader, reqBody)
if level := c.logResponse(ctx, "Response", resHeader, resBody, resCloseConn); level > diffLogLevel {
diffLogLevel = level
}
}
// log proxy response after the normal response to make it less confusing
if c.mode != NormalMode {
if level := c.logResponse(ctx, "Proxy response", proxyHeader, proxyBody, false); level > diffLogLevel {
diffLogLevel = level
}
}
// diff in diff mode
if c.l.Enabled(ctx, diffLogLevel) && (c.mode == DiffNormalMode || c.mode == DiffProxyMode) {
if err = c.logDiff(ctx, resHeader, proxyHeader, resBody, proxyBody, diffLogLevel); err != nil {
return err
}
}
// replace response with one from proxy in proxy and diff-proxy modes
if c.mode == ProxyMode || c.mode == DiffProxyMode {
resHeader = proxyHeader
resBody = proxyBody
}
if resHeader == nil || resBody == nil {
panic("no response to send to client")
}
if err = wire.WriteMessage(bufw, resHeader, resBody); err != nil {
c.l.DebugContext(ctx, "Failed to write message", logging.Error(err))
return err
if err = wire.WriteMessage(bufw, resp.WireHeader(), resp.WireBody()); err != nil {
return lazyerrors.Error(err)
}
if err = bufw.Flush(); err != nil {
c.l.DebugContext(ctx, "Failed to flush buffer", logging.Error(err))
return err
}
if resCloseConn {
err = errors.New("fatal error")
c.l.DebugContext(ctx, "Connection closed unexpectedly", logging.Error(err))
return err
return lazyerrors.Error(err)
}
return nil
}
// route sends request to a handler's command based on the op code provided in the request header.
//
// The passed context is canceled when the client disconnects.
//
// Handlers to which it routes, should not panic on bad input, but may do so in "impossible" cases.
// They also should not use recover(). That allows us to use fuzzing.
//
// Returned resBody can be nil.
func (c *conn) route(connCtx context.Context, reqHeader *wire.MsgHeader, reqBody wire.MsgBody) (resHeader *wire.MsgHeader, resBody wire.MsgBody, closeConn bool) { //nolint:lll // argument list is too long
var span oteltrace.Span
var command, result, argument string
defer func() {
if result == "" {
result = "panic"
}
if argument == "" {
argument = "unknown"
}
c.m.Responses.WithLabelValues(resHeader.OpCode.String(), command, argument, result).Inc()
must.NotBeZero(span)
if result != "ok" {
span.SetStatus(otelcodes.Error, result)
}
span.SetName(command)
span.SetAttributes(
otelattribute.String("db.ferretdb.opcode", resHeader.OpCode.String()),
otelattribute.Int("db.ferretdb.request_id", int(resHeader.ResponseTo)),
otelattribute.String("db.ferretdb.argument", argument),
)
span.End()
}()
resHeader = new(wire.MsgHeader)
var err error
switch reqHeader.OpCode {
case wire.OpCodeMsg:
msg := reqBody.(*wire.OpMsg)
resHeader.OpCode = wire.OpCodeMsg
var doc *wirebson.Document
if doc, err = msg.Section0(); err == nil {
command = doc.Command()
}
if err == nil {
comment, _ := doc.Get("comment").(string)
spanCtx, e := observability.SpanContextFromComment(comment)
if e == nil {
connCtx = oteltrace.ContextWithRemoteSpanContext(connCtx, spanCtx)
} else {
c.l.DebugContext(connCtx, "Failed to extract span context from comment", logging.Error(e))
}
}
connCtx, span = otel.Tracer("").Start(connCtx, "")
if err == nil {
var res *middleware.Response
if res, err = c.h.Handle(connCtx, middleware.RequestWire(reqHeader, msg)); res != nil {
resBody = res.OpMsg
}
}
case wire.OpCodeQuery:
query := reqBody.(*wire.OpQuery)
resHeader.OpCode = wire.OpCodeReply
var q *wirebson.Document
if q, err = query.Query(); err == nil {
command = q.Command()
}
connCtx, span = otel.Tracer("").Start(connCtx, "")
if err == nil {
var res *middleware.Response
if res, err = c.h.Handle(connCtx, middleware.RequestWire(reqHeader, query)); res != nil {
resBody = res.OpReply
}
}
case wire.OpCodeReply:
fallthrough
case wire.OpCodeUpdate:
fallthrough
case wire.OpCodeInsert:
fallthrough
case wire.OpCodeGetByOID:
fallthrough
case wire.OpCodeGetMore:
fallthrough
case wire.OpCodeDelete:
fallthrough
case wire.OpCodeKillCursors:
fallthrough
case wire.OpCodeCompressed:
connCtx, span = otel.Tracer("").Start(connCtx, "")
err = lazyerrors.Errorf("unhandled OpCode %s", reqHeader.OpCode)
default:
connCtx, span = otel.Tracer("").Start(connCtx, "")
err = lazyerrors.Errorf("unexpected OpCode %s", reqHeader.OpCode)
}
if command == "" {
command = "unknown"
}
c.m.Requests.WithLabelValues(reqHeader.OpCode.String(), command).Inc()
// set body for error
if err != nil {
switch resHeader.OpCode {
case wire.OpCodeMsg:
protoErr := mongoerrors.Make(connCtx, err, "", c.l)
resBody = protoErr.Msg()
result = protoErr.Name
argument = protoErr.Argument
case wire.OpCodeReply:
protoErr := mongoerrors.Make(connCtx, err, "", c.l)
resBody = protoErr.Reply()
result = protoErr.Name
argument = protoErr.Argument
case wire.OpCodeQuery:
fallthrough
case wire.OpCodeUpdate:
fallthrough
case wire.OpCodeInsert:
fallthrough
case wire.OpCodeGetByOID:
fallthrough
case wire.OpCodeGetMore:
fallthrough
case wire.OpCodeDelete:
fallthrough
case wire.OpCodeKillCursors:
fallthrough
case wire.OpCodeCompressed:
// do not panic to make fuzzing easier
closeConn = true
result = "unhandled"
c.l.ErrorContext(
connCtx,
"Handler error for unhandled response opcode",
logging.Error(err),
slog.Any("opcode", resHeader.OpCode),
)
return
default:
// do not panic to make fuzzing easier
closeConn = true
result = "unexpected"
c.l.ErrorContext(
connCtx,
"Handler error for unexpected response opcode",
logging.Error(err),
slog.Any("opcode", resHeader.OpCode),
)
return
}
}
// Don't call MarshalBinary there. Fix header in the caller?
// TODO https://github.com/FerretDB/FerretDB/issues/273
b, err := resBody.MarshalBinary()
if err != nil {
result = ""
panic(err)
}
resHeader.MessageLength = int32(wire.MsgHeaderLen + len(b))
resHeader.RequestID = c.lastRequestID.Add(1)
resHeader.ResponseTo = reqHeader.RequestID
if result == "" {
result = "ok"
}
return
}
// renamePartialFile takes over an open file `f` and closes it.
// It uses the given error to check if the connection was closed by the client,
// if so the given file is renamed to a name generated by hash,
@@ -569,90 +181,3 @@ func (c *conn) renamePartialFile(ctx context.Context, f *os.File, h hash.Hash, e
c.l.WarnContext(ctx, "Failed to rename file", logging.Error(e))
}
}
// logResponse logs response's header and body and returns the log level that was used.
//
// The param `who` will be used in logs and should represent the type of the response,
// for example "Response" or "Proxy Response".
func (c *conn) logResponse(ctx context.Context, who string, resHeader *wire.MsgHeader, resBody wire.MsgBody, closeConn bool) slog.Level { //nolint:lll // for readability
level := slog.LevelDebug
if resHeader.OpCode == wire.OpCodeMsg {
msg := resBody.(*wire.OpMsg)
doc, _ := msg.Section0()
var ok bool
if doc != nil {
switch v := doc.Get("ok").(type) {
case float64:
ok = v == 1
case int32:
ok = v == 1
case int64:
ok = v == 1
}
}
if !ok {
level = slog.LevelWarn
}
}
if closeConn {
level = slog.LevelError
}
if c.l.Enabled(ctx, level) {
c.l.Log(ctx, level, who+" header: "+resHeader.String())
c.l.Log(ctx, level, who+" message:\n"+resBody.StringIndent())
}
return level
}
// logDiff logs the diff between the response and the proxy response.
func (c *conn) logDiff(ctx context.Context, resHeader, proxyHeader *wire.MsgHeader, resBody, proxyBody wire.MsgBody, logLevel slog.Level) error { //nolint:lll // for readability
diffHeader, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(resHeader.String()),
FromFile: "res header",
B: difflib.SplitLines(proxyHeader.String()),
ToFile: "proxy header",
Context: 1,
})
if err != nil {
return err
}
// resBody can be nil if we got a message we could not handle at all, like unsupported OpQuery.
var resBodyString, proxyBodyString string
if resBody != nil {
resBodyString = resBody.StringIndent()
}
if proxyBody != nil {
proxyBodyString = proxyBody.StringIndent()
}
diffBody, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(resBodyString),
FromFile: "res body",
B: difflib.SplitLines(proxyBodyString),
ToFile: "proxy body",
Context: 1,
})
if err != nil {
return err
}
if len(diffBody) > 0 {
// the diff control lines (those with ---, +++, or @@) are created with a trailing newline
diffBody = "\n" + strings.TrimSpace(diffBody)
}
c.l.Log(ctx, logLevel, "Header diff:\n"+diffHeader+"\nBody diff:"+diffBody)
return nil
}
+12 -1
View File
@@ -24,6 +24,7 @@ import (
)
// ConnInfo represents client connection information.
// It must be one-to-one mapped to a client connection.
type ConnInfo struct {
// the order of fields is weird to make the struct smaller due to alignment
@@ -33,7 +34,8 @@ type ConnInfo struct {
metadataRecv bool // protected by rw
steps int // protected by rw
token *resource.Token
token *resource.Token
onClose func(*ConnInfo)
}
// New creates a new ConnInfo.
@@ -49,9 +51,18 @@ func New() *ConnInfo {
// Close untracks ConnInfo.
func (ci *ConnInfo) Close() {
if ci.onClose != nil {
ci.onClose(ci)
}
resource.Untrack(ci, ci.token)
}
// OnClose sets a callback to be called when ConnInfo is [Close]'d.
func (ci *ConnInfo) OnClose(f func(*ConnInfo)) {
ci.onClose = f
}
// Conv returns SCRAM conversation.
func (ci *ConnInfo) Conv() *scram.Conv {
ci.rw.RLock()
+63 -49
View File
@@ -17,23 +17,23 @@ package clientconn
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log/slog"
"math/rand"
"net"
"os"
"sync"
"time"
"github.com/AlekSi/lazyerrors"
"github.com/FerretDB/wire"
"github.com/prometheus/client_golang/prometheus"
"github.com/FerretDB/FerretDB/v2/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/v2/internal/handler"
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
"github.com/FerretDB/FerretDB/v2/internal/util/tlsutil"
)
// Listener listens on one or multiple interfaces (TCP, Unix, TLS sockets)
@@ -42,6 +42,7 @@ type Listener struct {
*ListenerOpts
ll *slog.Logger
lm *listenerMetrics
tcpListener net.Listener
unixListener net.Listener
@@ -52,9 +53,8 @@ type Listener struct {
// ListenerOpts represents listener configuration.
type ListenerOpts struct {
Handler *handler.Handler
Metrics *connmetrics.ListenerMetrics
Logger *slog.Logger
M *middleware.Middleware
Logger *slog.Logger
TCP string // empty value disables TCP listener
Unix string // empty value disables Unix listener
@@ -64,7 +64,7 @@ type ListenerOpts struct {
TLSKeyFile string
TLSCAFile string
Mode Mode
Mode middleware.Mode
ProxyAddr string
ProxyTLSCertFile string
ProxyTLSKeyFile string
@@ -73,14 +73,57 @@ type ListenerOpts struct {
TestRecordsDir string // if empty, no records are created
}
// tlsConfig provides server TLS configuration for the given certificate and key files.
// Passing caFile enables client certificate verification.
func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
if _, err := os.Stat(certFile); err != nil {
return nil, fmt.Errorf("TLS certificate file: %w", err)
}
if _, err := os.Stat(keyFile); err != nil {
return nil, fmt.Errorf("TLS key file: %w", err)
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("TLS file pair: %w", err)
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
}
if caFile != "" {
if _, err = os.Stat(caFile); err != nil {
return nil, fmt.Errorf("TLS CA file: %w", err)
}
var b []byte
if b, err = os.ReadFile(caFile); err != nil {
return nil, err
}
ca := x509.NewCertPool()
if ok := ca.AppendCertsFromPEM(b); !ok {
return nil, fmt.Errorf("TLS CA file: failed to parse")
}
config.ClientAuth = tls.RequireAndVerifyClientCert
config.ClientCAs = ca
}
return config, nil
}
// Listen creates a new listener and starts listening on configured interfaces.
// It takes over the passed handler.
// [Listener.Run] must be called on the returned value.
func Listen(opts *ListenerOpts) (l *Listener, err error) {
ll := logging.WithName(opts.Logger, "listener")
l = &Listener{
ListenerOpts: opts,
ll: ll,
lm: NewListenerMetrics(),
listenersClosed: make(chan struct{}),
}
@@ -114,7 +157,7 @@ func Listen(opts *ListenerOpts) (l *Listener, err error) {
if l.TLS != "" {
var config *tls.Config
if config, err = tlsutil.Config(l.TLSCertFile, l.TLSKeyFile, l.TLSCAFile); err != nil {
if config, err = tlsConfig(l.TLSCertFile, l.TLSKeyFile, l.TLSCAFile); err != nil {
err = lazyerrors.Error(err)
return
}
@@ -164,15 +207,6 @@ func (l *Listener) Listening() bool {
//
// When this method returns, listener and all connections are closed, and handler is stopped.
func (l *Listener) Run(ctx context.Context) {
// inherit ctx's values
handlerCtx, handlerCancel := context.WithCancel(context.WithoutCancel(ctx))
handlerDone := make(chan struct{})
go func() {
defer close(handlerDone)
l.Handler.Run(handlerCtx)
}()
var wg sync.WaitGroup
if l.tcpListener != nil {
@@ -218,11 +252,6 @@ func (l *Listener) Run(ctx context.Context) {
l.close()
l.ll.InfoContext(ctx, "Waiting for all connections to close")
wg.Wait()
// to properly handle last client commands like endSession,
// stop handler only after the last client disconnects
handlerCancel()
<-handlerDone
}
// acceptLoop runs listener's connection accepting loop until context is canceled.
@@ -236,7 +265,7 @@ func acceptLoop(ctx context.Context, listener net.Listener, wg *sync.WaitGroup,
return
}
l.Metrics.Accepts.WithLabelValues("1").Inc()
l.lm.accepts.WithLabelValues("1").Inc()
l.ll.WarnContext(ctx, "Failed to accept connection", logging.Error(err))
if !errors.Is(err, net.ErrClosed) {
@@ -247,7 +276,7 @@ func acceptLoop(ctx context.Context, listener net.Listener, wg *sync.WaitGroup,
}
wg.Add(1)
l.Metrics.Accepts.WithLabelValues("0").Inc()
l.lm.accepts.WithLabelValues("0").Inc()
go func() {
var connErr error
@@ -259,7 +288,7 @@ func acceptLoop(ctx context.Context, listener net.Listener, wg *sync.WaitGroup,
lv = "1"
}
l.Metrics.Durations.WithLabelValues(lv).Observe(time.Since(start).Seconds())
l.lm.durations.WithLabelValues(lv).Observe(time.Since(start).Seconds())
netConn.Close()
wg.Done()
}()
@@ -276,27 +305,14 @@ func acceptLoop(ctx context.Context, listener net.Listener, wg *sync.WaitGroup,
connID := fmt.Sprintf("%s -> %s", remoteAddr, netConn.LocalAddr())
opts := &newConnOpts{
netConn: netConn,
mode: l.Mode,
l: logging.WithName(l.ll, "// "+connID+" "), // derive from the original unnamed logger
handler: l.Handler,
connMetrics: l.Metrics.ConnMetrics, // share between all conns
proxyAddr: l.ProxyAddr,
proxyTLSCertFile: l.ProxyTLSCertFile,
proxyTLSKeyFile: l.ProxyTLSKeyFile,
proxyTLSCAFile: l.ProxyTLSCAFile,
//exhaustruct:enforce
conn := &conn{
netConn: netConn,
l: logging.WithName(l.ll, "// "+connID+" "),
m: l.M,
testRecordsDir: l.TestRecordsDir,
}
conn, connErr := newConn(opts)
if connErr != nil {
l.ll.WarnContext(connCtx, "Failed to create connection", slog.String("conn", connID), logging.Error(connErr))
return
}
l.ll.InfoContext(ctx, "Connection started", slog.String("conn", connID))
connErr = conn.run(connCtx)
@@ -342,14 +358,12 @@ func (l *Listener) TLSAddr() net.Addr {
// Describe implements [prometheus.Collector].
func (l *Listener) Describe(ch chan<- *prometheus.Desc) {
l.Metrics.Describe(ch)
l.Handler.Describe(ch)
l.lm.Describe(ch)
}
// Collect implements [prometheus.Collector].
func (l *Listener) Collect(ch chan<- prometheus.Metric) {
l.Metrics.Collect(ch)
l.Handler.Collect(ch)
l.lm.Collect(ch)
}
// check interfaces
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package connmetrics
package clientconn
import (
"time"
@@ -22,21 +22,21 @@ import (
// Parts of Prometheus metric names.
const (
// TODO https://github.com/FerretDB/FerretDB/issues/3420
namespace = "ferretdb"
subsystem = "client"
)
// ListenerMetrics represents listener metrics.
type ListenerMetrics struct {
Accepts *prometheus.CounterVec
Durations *prometheus.HistogramVec
ConnMetrics *ConnMetrics
// listenerMetrics represents listener metrics.
type listenerMetrics struct {
accepts *prometheus.CounterVec
durations *prometheus.HistogramVec
}
// NewListenerMetrics creates new listener metrics.
func NewListenerMetrics() *ListenerMetrics {
lm := &ListenerMetrics{
Accepts: prometheus.NewCounterVec(
func NewListenerMetrics() *listenerMetrics {
lm := &listenerMetrics{
accepts: prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
@@ -45,7 +45,7 @@ func NewListenerMetrics() *ListenerMetrics {
},
[]string{"error"},
),
Durations: prometheus.NewHistogramVec(
durations: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
@@ -64,31 +64,27 @@ func NewListenerMetrics() *ListenerMetrics {
},
[]string{"error"},
),
ConnMetrics: newConnMetrics(),
}
lm.Accepts.WithLabelValues("0")
lm.Durations.WithLabelValues("0")
lm.accepts.WithLabelValues("0")
lm.durations.WithLabelValues("0")
return lm
}
// Describe implements [prometheus.Collector].
func (lm *ListenerMetrics) Describe(ch chan<- *prometheus.Desc) {
lm.Accepts.Describe(ch)
lm.Durations.Describe(ch)
lm.ConnMetrics.Describe(ch)
func (lm *listenerMetrics) Describe(ch chan<- *prometheus.Desc) {
lm.accepts.Describe(ch)
lm.durations.Describe(ch)
}
// Collect implements [prometheus.Collector].
func (lm *ListenerMetrics) Collect(ch chan<- prometheus.Metric) {
lm.Accepts.Collect(ch)
lm.Durations.Collect(ch)
lm.ConnMetrics.Collect(ch)
func (lm *listenerMetrics) Collect(ch chan<- prometheus.Metric) {
lm.accepts.Collect(ch)
lm.durations.Collect(ch)
}
// check interfaces
var (
_ prometheus.Collector = (*ListenerMetrics)(nil)
_ prometheus.Collector = (*listenerMetrics)(nil)
)
+34
View File
@@ -14,4 +14,38 @@
package api
import _ "embed"
// Spec is the embedded OpenAPI specification.
//
//go:embed openapi.json
var Spec []byte
//go:generate ../../../bin/oapi-codegen --config=./oapi-config.yml ./openapi.json
// Response types are used to represent the responses from the API.
//
//sumtype:decl
type Response interface {
sealed()
}
func (r *AggregateResponseBody) sealed() {}
func (r *DeleteResponseBody) sealed() {}
func (r *Error) sealed() {}
func (r *FindOneResponseBody) sealed() {}
func (r *FindManyResponseBody) sealed() {}
func (r *InsertOneResponseBody) sealed() {}
func (r *InsertManyResponseBody) sealed() {}
func (r *UpdateResponseBody) sealed() {}
var (
_ Response = (*AggregateResponseBody)(nil)
_ Response = (*DeleteResponseBody)(nil)
_ Response = (*Error)(nil)
_ Response = (*FindOneResponseBody)(nil)
_ Response = (*FindManyResponseBody)(nil)
_ Response = (*InsertManyResponseBody)(nil)
_ Response = (*InsertOneResponseBody)(nil)
_ Response = (*UpdateResponseBody)(nil)
)
Loaded 100 of 578 files, more files were not shown because too many files have changed in this diff. Show more