Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c645ed1adf
|
||
|
|
87234d9f76
|
||
|
|
0d5eb3343a | ||
|
|
52ad11f0af | ||
|
|
71b6eb3a08 | ||
|
|
5cb62a1155 | ||
|
|
867537dd01 | ||
|
|
487880fd19 | ||
|
|
3b29cece0c | ||
|
|
c1bb0b6db6 | ||
|
|
a04b20a58f | ||
|
|
2b2ce550d4 | ||
|
|
87293d3434 | ||
|
|
7b0d200262 | ||
|
|
d1d037a030 | ||
|
|
c37a62b0a2 | ||
|
|
cff1577d19 | ||
|
|
efa50d4877 | ||
|
|
067eb2f143 | ||
|
|
4e8e51751d | ||
|
|
ae0b94de12 | ||
|
|
2c24483c06 | ||
|
|
221d5cbd08 | ||
|
|
62ce1019b1 | ||
|
|
ef9934e3ed | ||
|
|
e78eaffb58 | ||
|
|
370b1586c5 | ||
|
|
1f8d160296 | ||
|
|
3656ebd1c4 | ||
|
|
e3a7406b9e | ||
|
|
4847e59e25 | ||
|
|
b6fa806add | ||
|
|
5394f00550 | ||
|
|
9a48b7132c | ||
|
|
4cea93524d | ||
|
|
3cd4554d87 | ||
|
|
2e6ca25078 | ||
|
|
901a8f6030 | ||
|
|
b1feb6377d | ||
|
|
3fd8c982a5 | ||
|
|
c05de8bdea | ||
|
|
d97f2612b8 | ||
|
|
6d8e86b774 | ||
|
|
7a117e768d | ||
|
|
bfd1650ee2 | ||
|
|
d9a14c883e | ||
|
|
2b652dae20 | ||
|
|
6631594402 | ||
|
|
c5f69fe962 | ||
|
|
a65042d381 | ||
|
|
24981ad991 | ||
|
|
2078f859f3 | ||
|
|
16670ff34f | ||
|
|
b994d18792 | ||
|
|
4ea90be4aa | ||
|
|
3770c1e4eb | ||
|
|
5fd75c160a | ||
|
|
c60131a69c | ||
|
|
b4eda04f37 | ||
|
|
6e2d3affd9 | ||
|
|
2cbf8cac53 | ||
|
|
b933b1f365 | ||
|
|
96d34a4615 | ||
|
|
1a5a6ec3c3 | ||
|
|
997c02f379 | ||
|
|
2412bf3595 | ||
|
|
20697b6b74 | ||
|
|
cb18c69dcc | ||
|
|
e99a07db05 | ||
|
|
b1189f2594 | ||
|
|
783d39086c | ||
|
|
c87f9e3c18 | ||
|
|
e5615b35ca | ||
|
|
327a8fb759 | ||
|
|
319128c43f | ||
|
|
bccd6276dd | ||
|
|
1b5913f2bf | ||
|
|
66f9bc149d | ||
|
|
f06bf851a9 | ||
|
|
c891abbdf6 | ||
|
|
f65c7b643a | ||
|
|
bc0e9128e3 | ||
|
|
e1ea96acf4 | ||
|
|
f64490cf94 | ||
|
|
5a05644fed |
No files matched your search
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- [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.
|
||||
@@ -64,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
@@ -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.
|
||||
|
||||
@@ -20,6 +20,10 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
skip_flaky_check:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
GOPATH: /home/runner/go
|
||||
@@ -39,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
|
||||
@@ -73,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
|
||||
|
||||
@@ -91,17 +103,16 @@ 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
|
||||
@@ -112,7 +123,7 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./integration/integration-${{ inputs.task }}.txt
|
||||
flags: integration,${{ inputs.task }}-${{ inputs.shard_index }}
|
||||
flags: integration,${{ inputs.task }},${{ inputs.task }}-${{ inputs.shard_index }}
|
||||
disable_file_fixes: true
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
@@ -125,6 +136,20 @@ jobs:
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -172,6 +186,20 @@ jobs:
|
||||
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()
|
||||
@@ -199,17 +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 }}
|
||||
skip_flaky_check: ${{ matrix.skip_flaky_check || false }}
|
||||
|
||||
integration-no-xfail:
|
||||
name: postgresql no-xfail
|
||||
@@ -265,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
|
||||
@@ -325,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
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@ telemetry.json
|
||||
*.tmp
|
||||
*.txt
|
||||
!website/static/*.txt
|
||||
*.xml
|
||||
|
||||
.task/
|
||||
bin/
|
||||
|
||||
+2
-1
@@ -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:
|
||||
|
||||
+146
@@ -2,6 +2,152 @@
|
||||
|
||||
<!-- 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
|
||||
|
||||
+1
-1
@@ -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/).
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
FerretDB database system (https://www.ferretdb.com/).
|
||||
Copyright 2021 FerretDB Inc.
|
||||
@@ -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/).
|
||||
+73
-6
@@ -17,6 +17,8 @@ vars:
|
||||
BUILD_TAGS: ferretdb_dev
|
||||
MODE: diff-normal
|
||||
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
|
||||
@@ -113,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"
|
||||
@@ -194,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
|
||||
|
||||
@@ -258,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:
|
||||
@@ -275,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
|
||||
@@ -289,6 +337,7 @@ 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
|
||||
@@ -305,6 +354,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:5433/yugabyte'
|
||||
--otel-traces-url=http://127.0.0.1:4318/v1/traces
|
||||
@@ -319,6 +369,7 @@ 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:5433/yugabyte'
|
||||
--otel-traces-url=http://127.0.0.1:4318/v1/traces
|
||||
@@ -362,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:
|
||||
|
||||
@@ -1,3 +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 @@
|
||||
FROM ghcr.io/ferretdb/ferretdb-prettier:3.5.3-2
|
||||
FROM ghcr.io/ferretdb/ferretdb-prettier:3.6.2-1
|
||||
@@ -1 +1 @@
|
||||
FROM ghcr.io/ferretdb/ferretdb-textlint:14.8.4-1
|
||||
FROM ghcr.io/ferretdb/ferretdb-textlint:15.2.0-1
|
||||
@@ -1 +1 @@
|
||||
FROM ghcr.io/ferretdb/ferretdb-wrangler:4.20.0-1
|
||||
FROM ghcr.io/ferretdb/ferretdb-wrangler:4.24.0-1
|
||||
@@ -1 +1 @@
|
||||
FROM jaegertracing/all-in-one:1.70.0
|
||||
FROM jaegertracing/all-in-one:1.74.0
|
||||
@@ -1 +0,0 @@
|
||||
FROM ghcr.io/danny-avila/librechat:v0.7.8
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use development image and full tag close to the release.
|
||||
FROM ghcr.io/ferretdb/postgres-documentdb-dev:17-0.105.0-ferretdb-2.4.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,2 +1,2 @@
|
||||
FROM aquasec/trivy:0.63.0
|
||||
FROM aquasec/trivy:0.67.2
|
||||
WORKDIR /workdir
|
||||
@@ -1 +1 @@
|
||||
FROM yugabytedb/yugabyte:2.25.2.0-b359
|
||||
FROM yugabytedb/yugabyte:2025.1.1.0-b197
|
||||
@@ -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.105.0-ferretdb-2.4.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.5.linux-amd64.tar.gz
|
||||
tar -xf go1.24.5.linux-amd64.tar.gz
|
||||
rm go1.24.5.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
|
||||
|
||||
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
|
||||
|
||||
# prepare stage
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24.5 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.5 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.5 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
|
||||
|
||||
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
|
||||
|
||||
# prepare stage
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24.5 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.5 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.105.0-ferretdb-2.4.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
|
||||
|
||||
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
|
||||
|
||||
# prepare stage
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24.5 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.5 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.105.0-ferretdb-2.4.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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,8 @@ ARG LABEL_COMMIT
|
||||
|
||||
# prepare stage
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24.5 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.5 AS production-build
|
||||
# TODO https://github.com/FerretDB/FerretDB/issues/5449
|
||||
FROM golang:1.25.4-bookworm AS production-build
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"FerretDB": {
|
||||
"type": "remote",
|
||||
"url": "http://127.0.0.1:8081/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
setParameter:
|
||||
enableTestCommands: 1 # for sleep
|
||||
|
||||
net:
|
||||
port: 47017 # matches host's port for replica set to work
|
||||
bindIpAll: true
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
setParameter:
|
||||
enableTestCommands: 1 # for sleep
|
||||
|
||||
net:
|
||||
port: 37017 # matches host's port for replica set to work
|
||||
bindIpAll: true
|
||||
|
||||
Submodule build/postgres-documentdb/documentdb updated: 8453d93b4b...e63835403d.
@@ -18,18 +18,20 @@ import "runtime"
|
||||
|
||||
const (
|
||||
// DocumentDB is a version of DocumentDB this version of FerretDB is compatible with.
|
||||
DocumentDB = "0.105.0 gitref: HEAD sha:8453d93b 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.105.0-ferretdb-2.4.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, v2.3.1
|
||||
"0.105.0 gitref: HEAD sha:8453d93b buildId:0", // v2.4.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.
|
||||
@@ -41,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"
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@@ -30,7 +31,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -161,7 +161,7 @@ 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)
|
||||
|
||||
@@ -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{})
|
||||
|
||||
+58
-105
@@ -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,10 @@ 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"
|
||||
@@ -51,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"
|
||||
)
|
||||
@@ -79,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 {
|
||||
@@ -103,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"`
|
||||
@@ -249,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 {
|
||||
@@ -263,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.
|
||||
@@ -353,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() {
|
||||
@@ -381,7 +388,7 @@ func run() {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
if cmp.Or(cli.DebugAddr, "-") != "-" {
|
||||
if cli.DebugAddr != "" {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
@@ -410,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() {
|
||||
@@ -424,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))
|
||||
@@ -436,7 +442,7 @@ func run() {
|
||||
}()
|
||||
}
|
||||
|
||||
lm := connmetrics.NewListenerMetrics()
|
||||
mm := middleware.NewMetrics()
|
||||
|
||||
{
|
||||
wg.Add(1)
|
||||
@@ -453,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,
|
||||
@@ -466,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: middleware.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()
|
||||
|
||||
|
||||
+10
-5
@@ -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
|
||||
|
||||
+1
-6
@@ -31,6 +31,7 @@ services:
|
||||
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: >
|
||||
@@ -123,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
|
||||
|
||||
+35
-42
@@ -35,12 +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"
|
||||
)
|
||||
@@ -74,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.
|
||||
@@ -107,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/",
|
||||
@@ -116,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,
|
||||
@@ -125,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: middleware.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
|
||||
}
|
||||
|
||||
@@ -181,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()
|
||||
@@ -198,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: "/",
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,70 +1,73 @@
|
||||
module github.com/FerretDB/FerretDB/v2
|
||||
|
||||
go 1.24
|
||||
go 1.25
|
||||
|
||||
toolchain go1.24.5
|
||||
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.7-0.20250708055527-67b1f7c98628
|
||||
github.com/alecthomas/kong v1.12.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.65.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.37.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0
|
||||
go.opentelemetry.io/otel/sdk v1.37.0
|
||||
go.opentelemetry.io/otel/trace v1.37.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.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.27.1 // 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.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.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-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // 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
|
||||
)
|
||||
@@ -1,23 +1,25 @@
|
||||
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.7-0.20250708055527-67b1f7c98628 h1:QqqnK0XPruJ4Z2XyjaYZn+Up396LFCBFb/mE4u+B7dk=
|
||||
github.com/FerretDB/wire v0.1.7-0.20250708055527-67b1f7c98628/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.12.0 h1:oKd/0fHSdajj5PfGDd3ScvEvpVJf9mT2mb5r9xYadYM=
|
||||
github.com/alecthomas/kong v1.12.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=
|
||||
@@ -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.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.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.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||
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-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
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=
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:59 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
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
+39
-33
@@ -1,70 +1,76 @@
|
||||
module github.com/FerretDB/FerretDB/v2/integration
|
||||
|
||||
go 1.24
|
||||
go 1.25
|
||||
|
||||
toolchain go1.24.5
|
||||
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.7-0.20250708055527-67b1f7c98628
|
||||
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.62.0
|
||||
go.opentelemetry.io/otel v1.37.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.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.27.1 // 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.65.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.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.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-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // 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
|
||||
)
|
||||
+86
-68
@@ -1,15 +1,21 @@
|
||||
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.7-0.20250708055527-67b1f7c98628 h1:QqqnK0XPruJ4Z2XyjaYZn+Up396LFCBFb/mE4u+B7dk=
|
||||
github.com/FerretDB/wire v0.1.7-0.20250708055527-67b1f7c98628/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=
|
||||
@@ -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.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.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.62.0 h1:IDI0wUpSFq/RUr1rRTHT7nF/Mr3V4kENTn05P39fH7k=
|
||||
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.62.0/go.mod h1:PxUlDgXfAHM+OrUrqs3pbc2OR59ZLDSe9r5NiS0B/4E=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||
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-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
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=
|
||||
|
||||
@@ -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/microsoft/documentdb/issues/25
|
||||
// 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/microsoft/documentdb/issues/25
|
||||
// TODO https://github.com/documentdb/documentdb/issues/25
|
||||
// failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/296",
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -17,6 +17,7 @@ package session
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/FerretDB/wire"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -36,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/")
|
||||
|
||||
@@ -25,11 +25,8 @@ import (
|
||||
"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/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/testutil"
|
||||
)
|
||||
@@ -86,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":
|
||||
@@ -110,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,
|
||||
TCPAddr: "",
|
||||
UnixAddr: "",
|
||||
TLSAddr: "",
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSCAFile: "",
|
||||
Mode: middleware.NormalMode,
|
||||
ProxyAddr: *targetProxyAddrF,
|
||||
TestRecordsDir: testutil.TmpRecordsDir,
|
||||
}
|
||||
|
||||
if *targetProxyAddrF != "" {
|
||||
listenerOpts.Mode = middleware.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
|
||||
@@ -174,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)
|
||||
|
||||
+60
-16
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
-481
@@ -29,89 +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"
|
||||
)
|
||||
|
||||
// conn represents client connection.
|
||||
type conn struct {
|
||||
netConn net.Conn
|
||||
mode middleware.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 middleware.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 != middleware.NormalMode {
|
||||
var err error
|
||||
|
||||
p, err = proxy.New(opts.proxyAddr, opts.proxyTLSCertFile, opts.proxyTLSKeyFile, opts.proxyTLSCAFile)
|
||||
if 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.
|
||||
//
|
||||
@@ -119,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)
|
||||
@@ -197,315 +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 != middleware.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()
|
||||
proxyBody = resp.WireBody()
|
||||
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 != middleware.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 != middleware.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 == middleware.DiffNormalMode || c.mode == middleware.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 == middleware.ProxyMode || c.mode == middleware.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.WireBody()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -540,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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -17,24 +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)
|
||||
@@ -43,6 +42,7 @@ type Listener struct {
|
||||
*ListenerOpts
|
||||
|
||||
ll *slog.Logger
|
||||
lm *listenerMetrics
|
||||
|
||||
tcpListener net.Listener
|
||||
unixListener net.Listener
|
||||
@@ -53,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
|
||||
@@ -74,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{}),
|
||||
}
|
||||
|
||||
@@ -115,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
|
||||
}
|
||||
@@ -165,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 {
|
||||
@@ -219,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.
|
||||
@@ -237,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) {
|
||||
@@ -248,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
|
||||
@@ -260,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()
|
||||
}()
|
||||
@@ -277,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)
|
||||
@@ -343,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
|
||||
|
||||
+19
-23
@@ -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)
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
+12
-12
@@ -2,7 +2,7 @@
|
||||
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
@@ -39,7 +39,7 @@ type AggregateRequestBody struct {
|
||||
// AggregateResponseBody defines model for AggregateResponseBody.
|
||||
type AggregateResponseBody struct {
|
||||
// Documents An array that contains the result set of the aggregation.
|
||||
Documents []map[string]interface{} `json:"documents"`
|
||||
Documents json.RawMessage `json:"documents"`
|
||||
}
|
||||
|
||||
// DeleteRequestBody defines model for DeleteRequestBody.
|
||||
@@ -80,8 +80,8 @@ type Error struct {
|
||||
|
||||
// ErrorMissingAuthenticationParameter defines model for ErrorMissingAuthenticationParameter.
|
||||
type ErrorMissingAuthenticationParameter struct {
|
||||
Error *interface{} `json:"error,omitempty"`
|
||||
ErrorCode *interface{} `json:"error_code,omitempty"`
|
||||
Error interface{} `json:"error,omitempty"`
|
||||
ErrorCode interface{} `json:"error_code,omitempty"`
|
||||
|
||||
// Link A link to a [log entry](https://www.mongodb.com/docs/atlas/app-services/logs/endpoint/) for the failed operation.
|
||||
Link string `json:"link,omitempty"`
|
||||
@@ -89,8 +89,8 @@ type ErrorMissingAuthenticationParameter struct {
|
||||
|
||||
// ErrorNoAuthenticationSpecified defines model for ErrorNoAuthenticationSpecified.
|
||||
type ErrorNoAuthenticationSpecified struct {
|
||||
Error *interface{} `json:"error,omitempty"`
|
||||
ErrorCode *interface{} `json:"error_code,omitempty"`
|
||||
Error interface{} `json:"error,omitempty"`
|
||||
ErrorCode interface{} `json:"error_code,omitempty"`
|
||||
|
||||
// Link A link to a [log entry](https://www.mongodb.com/docs/atlas/app-services/logs/endpoint/) for the failed operation.
|
||||
Link string `json:"link,omitempty"`
|
||||
@@ -98,8 +98,8 @@ type ErrorNoAuthenticationSpecified struct {
|
||||
|
||||
// ErrorUserNotFound defines model for ErrorUserNotFound.
|
||||
type ErrorUserNotFound struct {
|
||||
Error *interface{} `json:"error,omitempty"`
|
||||
ErrorCode *interface{} `json:"error_code,omitempty"`
|
||||
Error interface{} `json:"error,omitempty"`
|
||||
ErrorCode interface{} `json:"error_code,omitempty"`
|
||||
|
||||
// Link A link to a [log entry](https://www.mongodb.com/docs/atlas/app-services/logs/endpoint/) for the failed operation.
|
||||
Link string `json:"link,omitempty"`
|
||||
@@ -144,7 +144,7 @@ type FindManyRequestBody struct {
|
||||
// FindManyResponseBody The result of a find operation.
|
||||
type FindManyResponseBody struct {
|
||||
// Documents A list of documents that match the specified filter.
|
||||
Documents *[]map[string]interface{} `json:"documents,omitempty"`
|
||||
Documents *json.RawMessage `json:"documents,omitempty"`
|
||||
}
|
||||
|
||||
// FindOneRequestBody defines model for FindOneRequestBody.
|
||||
@@ -171,7 +171,7 @@ type FindOneRequestBody struct {
|
||||
// FindOneResponseBody The result of a findOne operation.
|
||||
type FindOneResponseBody struct {
|
||||
// Document A document that matches the specified filter. If no documents match, this is `null`.
|
||||
Document *map[string]interface{} `json:"document"`
|
||||
Document *json.RawMessage `json:"document"`
|
||||
}
|
||||
|
||||
// InsertManyRequestBody defines model for InsertManyRequestBody.
|
||||
@@ -195,7 +195,7 @@ type InsertManyRequestBody struct {
|
||||
// InsertManyResponseBody The result of an insertMany operation.
|
||||
type InsertManyResponseBody struct {
|
||||
// InsertedIds A list of the `_id` values of the inserted documents.
|
||||
InsertedIds *[]string `json:"insertedIds,omitempty"`
|
||||
InsertedIds *[]any `json:"insertedIds,omitempty"`
|
||||
}
|
||||
|
||||
// InsertOneRequestBody defines model for InsertOneRequestBody.
|
||||
@@ -219,7 +219,7 @@ type InsertOneRequestBody struct {
|
||||
// InsertOneResponseBody The result of an insertOne operation.
|
||||
type InsertOneResponseBody struct {
|
||||
// InsertedId The `_id` value of the inserted document.
|
||||
InsertedId *interface{} `json:"insertedId,omitempty"`
|
||||
InsertedId *any `json:"insertedId,omitempty"`
|
||||
}
|
||||
|
||||
// Limit defines model for Limit.
|
||||
|
||||
@@ -1263,6 +1263,7 @@
|
||||
"description": "The result of a findOne operation.",
|
||||
"properties": {
|
||||
"document": {
|
||||
"x-go-type": "json.RawMessage",
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "A document that matches the specified filter. If no documents match, this is `null`."
|
||||
@@ -1404,6 +1405,7 @@
|
||||
"properties": {
|
||||
"documents": {
|
||||
"type": "array",
|
||||
"x-go-type": "json.RawMessage",
|
||||
"items": {
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1438,6 +1440,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"insertedId": {
|
||||
"x-go-type": "any",
|
||||
"description": "The `_id` value of the inserted document."
|
||||
}
|
||||
}
|
||||
@@ -1475,7 +1478,7 @@
|
||||
"type": "array",
|
||||
"description": "A list of the `_id` values of the inserted documents.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-go-type": "any",
|
||||
"description": "The `_id` value of an inserted document."
|
||||
}
|
||||
}
|
||||
@@ -1587,6 +1590,7 @@
|
||||
"documents": {
|
||||
"description": "An array that contains the result set of the aggregation.",
|
||||
"type": "array",
|
||||
"x-go-type": "json.RawMessage",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A document included in the result set of the aggregation."
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2021 FerretDB Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package dataapi provides a Data API wrapper,
|
||||
// which allows FerretDB to be used over HTTP instead of MongoDB wire protocol.
|
||||
package dataapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/server"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
)
|
||||
|
||||
// Listener represents Data API TCP listener and HTTP handler.
|
||||
type Listener struct {
|
||||
opts *ListenOpts
|
||||
lis net.Listener
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
// ListenOpts represents [Listen] options.
|
||||
type ListenOpts struct {
|
||||
L *slog.Logger
|
||||
M *middleware.Middleware
|
||||
TCPAddr string
|
||||
Auth bool
|
||||
}
|
||||
|
||||
// Listen creates a new Data API handler and starts listener on the given TCP address.
|
||||
// [Listener.Run] must be called on the returned value.
|
||||
func Listen(opts *ListenOpts) (*Listener, error) {
|
||||
lis, err := net.Listen("tcp", opts.TCPAddr)
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
s := server.New(opts.L, opts.M)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /openapi.json", s.OpenAPISpec)
|
||||
|
||||
h := api.HandlerFromMux(s, mux)
|
||||
if opts.Auth {
|
||||
h = s.AuthMiddleware(h)
|
||||
}
|
||||
|
||||
h = s.ConnInfoMiddleware(h)
|
||||
|
||||
return &Listener{
|
||||
opts: opts,
|
||||
lis: lis,
|
||||
h: h,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run runs Data API handler until ctx is canceled.
|
||||
//
|
||||
// It exits when handler is stopped and listener closed.
|
||||
func (lis *Listener) Run(ctx context.Context) {
|
||||
s := &http.Server{
|
||||
Handler: lis.h,
|
||||
ErrorLog: slog.NewLogLogger(lis.opts.L.Handler(), slog.LevelError),
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
l := lis.opts.L
|
||||
|
||||
l.InfoContext(ctx, fmt.Sprintf("Starting Data API server on http://%s/", lis.Addr()))
|
||||
l.InfoContext(ctx, fmt.Sprintf("http://%s/openapi.json - OpenAPI spec", lis.Addr()))
|
||||
|
||||
go func() {
|
||||
if err := s.Serve(lis.lis); !errors.Is(err, http.ErrServerClosed) {
|
||||
l.LogAttrs(ctx, logging.LevelDPanic, "Serve exited with unexpected error", logging.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
// ctx is already canceled, but we want to inherit its values
|
||||
shutdownCtx, shutdownCancel := ctxutil.WithDelay(ctx)
|
||||
defer shutdownCancel(nil)
|
||||
|
||||
if err := s.Shutdown(shutdownCtx); err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelDPanic, "Shutdown exited with unexpected error", logging.Error(err))
|
||||
}
|
||||
|
||||
if err := s.Close(); err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelDPanic, "Close exited with unexpected error", logging.Error(err))
|
||||
}
|
||||
|
||||
l.InfoContext(ctx, "Data API server stopped")
|
||||
}
|
||||
|
||||
// Addr returns TCP listener's address.
|
||||
// It can be used to determine an actually used port, if it was zero.
|
||||
func (lis *Listener) Addr() net.Addr {
|
||||
return lis.lis.Addr()
|
||||
}
|
||||
|
||||
// Describe implements [prometheus.Collector].
|
||||
func (lis *Listener) Describe(ch chan<- *prometheus.Desc) {
|
||||
}
|
||||
|
||||
// Collect implements [prometheus.Collector].
|
||||
func (lis *Listener) Collect(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
@@ -12,16 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dataapi
|
||||
package dataapi_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,12 +29,8 @@ import (
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
|
||||
"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/testutil"
|
||||
)
|
||||
@@ -61,6 +57,22 @@ func TestSmokeDataAPI(t *testing.T) {
|
||||
assert.JSONEq(t, `{"documents":[]}`, string(body))
|
||||
})
|
||||
|
||||
t.Run("FindOneEmpty", func(t *testing.T) {
|
||||
jsonBody := `{
|
||||
"database": "` + db + `",
|
||||
"collection": "` + coll + `",
|
||||
"filter": {}
|
||||
}`
|
||||
|
||||
res, err := postJSON(t, "http://"+addr+"/action/findOne", jsonBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"document":null}`, string(body))
|
||||
})
|
||||
|
||||
docs := []string{
|
||||
`{"_id":1,"v":"foo"}`,
|
||||
`{"_id":2,"v":42}`,
|
||||
@@ -80,7 +92,7 @@ func TestSmokeDataAPI(t *testing.T) {
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"n":1}`, string(body))
|
||||
assert.JSONEq(t, `{"insertedId":1}`, string(body))
|
||||
})
|
||||
|
||||
t.Run("InsertMany", func(t *testing.T) {
|
||||
@@ -96,7 +108,7 @@ func TestSmokeDataAPI(t *testing.T) {
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"n":1}`, string(body))
|
||||
assert.JSONEq(t, `{"insertedIds": [2, 3]}`, string(body))
|
||||
})
|
||||
|
||||
t.Run("UpdateOne", func(t *testing.T) {
|
||||
@@ -212,6 +224,144 @@ func TestSmokeDataAPI(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"documents":[`+docs[2]+`]}`, string(body))
|
||||
})
|
||||
|
||||
t.Run("InsertOneEmptyId", func(t *testing.T) {
|
||||
jsonBody := `{
|
||||
"database": "` + db + `",
|
||||
"collection": "` + coll + `",
|
||||
"document": ` + `{"v":"foo"}` + `
|
||||
}`
|
||||
|
||||
res, err := postJSON(t, "http://"+addr+"/action/insertOne", jsonBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := strings.TrimSpace(string(body))
|
||||
assert.Regexp(t,
|
||||
`^{"insertedId":"[0-9a-f]{24}"}$`,
|
||||
bodyStr,
|
||||
"expected %q, got %q",
|
||||
`{"insertedId":"<ObjectID>"}`,
|
||||
string(body),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("UpsertOne", func(t *testing.T) {
|
||||
jsonBody := `{
|
||||
"database": "` + db + `",
|
||||
"collection": "` + coll + `",
|
||||
"filter": {"v":"non-existent"},
|
||||
"update": {"$set":{"v":"foo"}},
|
||||
"upsert": true
|
||||
}`
|
||||
|
||||
res, err := postJSON(t, "http://"+addr+"/action/updateOne", jsonBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := strings.TrimSpace(string(body))
|
||||
assert.Regexp(t,
|
||||
`^{"matchedCount":1,"modifiedCount":0,"upsertedId":".*[0-9a-f]{24}.*"}$`,
|
||||
bodyStr,
|
||||
"expected %q, got %q",
|
||||
`{"upsertedId":"<ObjectID>"}`,
|
||||
string(body),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("UpsertOneStringId", func(t *testing.T) {
|
||||
jsonBody := `{
|
||||
"database": "` + db + `",
|
||||
"collection": "` + coll + `",
|
||||
"filter": {"v":"non-existent"},
|
||||
"update": {"$set":{"_id":"string_id","v":"foo"}},
|
||||
"upsert": true
|
||||
}`
|
||||
|
||||
res, err := postJSON(t, "http://"+addr+"/action/updateOne", jsonBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{"matchedCount":1,"modifiedCount":0,"upsertedId":"string_id"}`, string(body))
|
||||
})
|
||||
|
||||
t.Run("UpsertMany", func(t *testing.T) {
|
||||
jsonBody := `{
|
||||
"database": "` + db + `",
|
||||
"collection": "` + coll + `",
|
||||
"filter": {"v":"non-existent"},
|
||||
"update": {"$set":{"v":"foo"}},
|
||||
"upsert": true
|
||||
}`
|
||||
|
||||
res, err := postJSON(t, "http://"+addr+"/action/updateMany", jsonBody)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := strings.TrimSpace(string(body))
|
||||
assert.Regexp(t,
|
||||
`^{"matchedCount":1,"modifiedCount":0,"upsertedId":".*[0-9a-f]{24}.*"}$`,
|
||||
bodyStr,
|
||||
"expected %q, got %q",
|
||||
`{"upsertedId":"<ObjectID>"}`,
|
||||
string(body),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
addr, _ := setupDataAPI(t, false)
|
||||
|
||||
t.Run("Spec", func(t *testing.T) {
|
||||
resp, err := http.Get("http://" + addr + "/openapi.json")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, resp.Body.Close())
|
||||
})
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
||||
assert.Equal(t, "53813", resp.Header.Get("Content-Length"))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var spec map[string]any
|
||||
err = json.Unmarshal(body, &spec)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "3.0.0", spec["openapi"])
|
||||
assert.Contains(t, spec, "info")
|
||||
assert.Contains(t, spec, "paths")
|
||||
|
||||
info := spec["info"].(map[string]any)
|
||||
assert.Equal(t, "FerretDB Data API", info["title"])
|
||||
})
|
||||
|
||||
t.Run("MethodNotAllowed", func(t *testing.T) {
|
||||
resp, err := http.Post("http://"+addr+"/openapi.json", "", nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, resp.Body.Close())
|
||||
})
|
||||
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
// postJSON sends POST request with provided JSON to data API under provided uri.
|
||||
@@ -240,65 +390,55 @@ func setupDataAPI(tb testing.TB, auth bool) (addr string, dbName string) {
|
||||
|
||||
l := testutil.Logger(tb)
|
||||
|
||||
p, err := documentdb.NewPool(uri, logging.WithName(l, "pool"), sp)
|
||||
require.NoError(tb, err)
|
||||
|
||||
handlerOpts := &handler.NewOpts{
|
||||
Pool: p,
|
||||
Auth: auth,
|
||||
|
||||
L: logging.WithName(l, "handler"),
|
||||
//exhaustruct:enforce
|
||||
res := setup.Setup(tb.Context(), &setup.SetupOpts{
|
||||
Logger: l,
|
||||
StateProvider: sp,
|
||||
}
|
||||
Metrics: middleware.NewMetrics(),
|
||||
|
||||
h, err := handler.New(handlerOpts)
|
||||
require.NoError(tb, err)
|
||||
PostgreSQLURL: uri,
|
||||
Auth: auth,
|
||||
ReplSetName: "",
|
||||
SessionCleanupInterval: 0,
|
||||
|
||||
listenerOpts := clientconn.ListenerOpts{
|
||||
Handler: h,
|
||||
Metrics: connmetrics.NewListenerMetrics(),
|
||||
Logger: logging.WithName(l, "listener"),
|
||||
TCP: "127.0.0.1:0",
|
||||
Mode: middleware.NormalMode,
|
||||
}
|
||||
ProxyAddr: "",
|
||||
ProxyTLSCertFile: "",
|
||||
ProxyTLSKeyFile: "",
|
||||
ProxyTLSCAFile: "",
|
||||
|
||||
lis, err := clientconn.Listen(&listenerOpts)
|
||||
require.NoError(tb, err)
|
||||
TCPAddr: "127.0.0.1:0",
|
||||
UnixAddr: "",
|
||||
TLSAddr: "",
|
||||
TLSCertFile: "",
|
||||
TLSKeyFile: "",
|
||||
TLSCAFile: "",
|
||||
Mode: middleware.NormalMode,
|
||||
TestRecordsDir: "",
|
||||
|
||||
var apiLis *Listener
|
||||
apiLis, err = Listen(&ListenOpts{
|
||||
TCPAddr: "127.0.0.1:0",
|
||||
L: logging.WithName(l, "dataapi"),
|
||||
Handler: h,
|
||||
DataAPIAddr: "127.0.0.1:0",
|
||||
|
||||
MCPAddr: "",
|
||||
})
|
||||
require.NoError(tb, err)
|
||||
require.NotNil(tb, res)
|
||||
|
||||
ctx, cancel := context.WithCancel(testutil.Ctx(tb))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
runDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(runDone)
|
||||
res.Run(ctx)
|
||||
}()
|
||||
|
||||
// ensure that all listener's and handler's logs are written before test ends
|
||||
tb.Cleanup(func() {
|
||||
cancel()
|
||||
wg.Wait()
|
||||
<-runDone
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
lis.Run(ctx)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
apiLis.Run(ctx)
|
||||
}()
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: lis.TCPAddr().String(),
|
||||
Host: res.WireListener.TCPAddr().String(),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
@@ -309,7 +449,7 @@ func setupDataAPI(tb testing.TB, auth bool) (addr string, dbName string) {
|
||||
client, err := mongo.Connect(options.Client().ApplyURI(u.String()))
|
||||
require.NoError(tb, err)
|
||||
|
||||
addr = apiLis.lis.Addr().String()
|
||||
addr = res.DataAPIListener.Addr().String()
|
||||
dbName = testutil.DatabaseName(tb)
|
||||
|
||||
err = client.Database(dbName).Drop(ctx)
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright 2021 FerretDB Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package dataapi provides a Data API wrapper,
|
||||
// which allows FerretDB to be used over HTTP instead of MongoDB wire protocol.
|
||||
package dataapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/server"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/handler"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
)
|
||||
|
||||
// Listener represents dataapi listener.
|
||||
type Listener struct {
|
||||
opts *ListenOpts
|
||||
lis net.Listener
|
||||
srv *server.Server
|
||||
}
|
||||
|
||||
// ListenOpts represents [Listen] options.
|
||||
type ListenOpts struct {
|
||||
L *slog.Logger
|
||||
Handler *handler.Handler
|
||||
TCPAddr string
|
||||
}
|
||||
|
||||
// Listen creates a new dataapi handler and starts listener on the given TCP address.
|
||||
func Listen(opts *ListenOpts) (*Listener, error) {
|
||||
lis, err := net.Listen("tcp", opts.TCPAddr)
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
return &Listener{
|
||||
opts: opts,
|
||||
lis: lis,
|
||||
srv: server.New(opts.L, opts.Handler),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run runs dataapi handler until ctx is canceled.
|
||||
//
|
||||
// It exits when handler is stopped and listener closed.
|
||||
func (lis *Listener) Run(ctx context.Context) {
|
||||
srvHandler := api.HandlerFromMux(lis.srv, http.NewServeMux())
|
||||
|
||||
if lis.opts.Handler.Auth {
|
||||
srvHandler = lis.srv.AuthMiddleware(srvHandler)
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: lis.srv.ConnInfoMiddleware(srvHandler),
|
||||
ErrorLog: slog.NewLogLogger(lis.opts.L.Handler(), slog.LevelError),
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
lis.opts.L.InfoContext(ctx, fmt.Sprintf("Starting DataAPI server on http://%s/", lis.lis.Addr()))
|
||||
|
||||
go func() {
|
||||
if err := srv.Serve(lis.lis); !errors.Is(err, http.ErrServerClosed) {
|
||||
lis.opts.L.LogAttrs(ctx, logging.LevelDPanic, "Serve exited with unexpected error", logging.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4848
|
||||
<-ctx.Done()
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// Aggregate implements [ServerInterface].
|
||||
func (s *Server) Aggregate(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) Aggregate(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,7 +37,7 @@ func (s *Server) Aggregate(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.AggregateRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,23 +48,33 @@ func (s *Server) Aggregate(w http.ResponseWriter, r *http.Request) {
|
||||
"cursor", wirebson.MustDocument(),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resMsg, err := s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resRaw := must.NotFail(resMsg.OpMsg.DocumentRaw())
|
||||
cursor := must.NotFail(resRaw.Decode()).Get("cursor").(wirebson.AnyDocument)
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
cursor := resp.Document().Get("cursor").(wirebson.AnyDocument)
|
||||
firstBatch := must.NotFail(cursor.Decode()).Get("firstBatch").(wirebson.AnyArray)
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"documents", firstBatch,
|
||||
)
|
||||
b, err := marshalSingleJSON(firstBatch)
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
res := api.AggregateResponseBody{
|
||||
Documents: b,
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// DeleteMany implements [ServerInterface].
|
||||
func (s *Server) DeleteMany(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) DeleteMany(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,7 +37,7 @@ func (s *Server) DeleteMany(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.DeleteRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *Server) DeleteMany(w http.ResponseWriter, r *http.Request) {
|
||||
"limit", float64(0),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -56,21 +56,24 @@ func (s *Server) DeleteMany(w http.ResponseWriter, r *http.Request) {
|
||||
"deletes", wirebson.MustArray(deleteDoc),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resMsg, err := s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resDoc := must.NotFail(must.NotFail(resMsg.OpMsg.DocumentRaw()).Decode())
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"deletedCount", resDoc.Get("n"),
|
||||
)
|
||||
res := api.DeleteResponseBody{
|
||||
DeletedCount: resp.Document().Get("n"),
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// DeleteOne implements [ServerInterface].
|
||||
func (s *Server) DeleteOne(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) DeleteOne(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,7 +37,7 @@ func (s *Server) DeleteOne(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.DeleteRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *Server) DeleteOne(w http.ResponseWriter, r *http.Request) {
|
||||
"limit", float64(1),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -56,21 +56,24 @@ func (s *Server) DeleteOne(w http.ResponseWriter, r *http.Request) {
|
||||
"deletes", wirebson.MustArray(deleteDoc),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resMsg, err := s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resDoc := must.NotFail(must.NotFail(resMsg.OpMsg.DocumentRaw()).Decode())
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"deletedCount", resDoc.Get("n"),
|
||||
)
|
||||
res := api.DeleteResponseBody{
|
||||
DeletedCount: resp.Document().Get("n"),
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -38,10 +38,11 @@ var (
|
||||
|
||||
// writeError encodes [api.Error] into JSON and writes it to w
|
||||
// with provided HTTP status code.
|
||||
func writeError(w http.ResponseWriter, err api.Error, code int) {
|
||||
w.WriteHeader(code)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4965
|
||||
func writeError(rw http.ResponseWriter, err api.Error, code int) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// ignore error, as writing to connection may fail if the client disconnects
|
||||
_ = json.NewEncoder(w).Encode(err)
|
||||
rw.WriteHeader(code)
|
||||
|
||||
_ = json.NewEncoder(rw).Encode(err)
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// Find implements [ServerInterface].
|
||||
func (s *Server) Find(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) Find(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,7 +37,7 @@ func (s *Server) Find(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.FindManyRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,22 +51,33 @@ func (s *Server) Find(w http.ResponseWriter, r *http.Request) {
|
||||
"sort", req.Sort,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resMsg, err := s.handler.Handle(ctx, msg)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
cursor := resp.Document().Get("cursor").(wirebson.AnyDocument)
|
||||
firstBatch := must.NotFail(cursor.Decode()).Get("firstBatch").(wirebson.AnyArray)
|
||||
|
||||
b, err := marshalSingleJSON(firstBatch)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resRaw := must.NotFail(resMsg.OpMsg.DocumentRaw())
|
||||
cursor := must.NotFail(resRaw.Decode()).Get("cursor").(wirebson.AnyDocument)
|
||||
res := api.FindManyResponseBody{
|
||||
Documents: &b,
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"documents", must.NotFail(cursor.Decode()).Get("firstBatch"),
|
||||
)
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// FindOne implements [ServerInterface].
|
||||
func (s *Server) FindOne(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) FindOne(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,7 +37,7 @@ func (s *Server) FindOne(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.FindOneRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,30 +49,41 @@ func (s *Server) FindOne(w http.ResponseWriter, r *http.Request) {
|
||||
"limit", float64(1),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resMsg, err := s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resRaw := must.NotFail(resMsg.OpMsg.DocumentRaw())
|
||||
cursor := must.NotFail(resRaw.Decode()).Get("cursor").(wirebson.AnyDocument)
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
cursor := resp.Document().Get("cursor").(wirebson.AnyDocument)
|
||||
firstBatch := must.NotFail(cursor.Decode()).Get("firstBatch").(wirebson.AnyArray)
|
||||
|
||||
var doc wirebson.RawDocument
|
||||
var res api.FindOneResponseBody
|
||||
|
||||
docs := must.NotFail(firstBatch.Decode())
|
||||
if docs.Len() > 0 {
|
||||
doc = must.NotFail(docs.Get(0).(wirebson.AnyDocument).Encode())
|
||||
if docs.Len() == 0 {
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
return
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"document", doc,
|
||||
)
|
||||
doc := docs.Get(0).(wirebson.AnyDocument)
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
b, err := marshalSingleJSON(doc)
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res.Document = &b
|
||||
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// InsertMany implements [ServerInterface].
|
||||
func (s *Server) InsertMany(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) InsertMany(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,29 +37,79 @@ func (s *Server) InsertMany(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.InsertManyRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
docsArr, err := unmarshalSingleJSON(&req.Documents)
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
documents, err := docsArr.(wirebson.RawArray).Decode()
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var insertedIds []any
|
||||
|
||||
for i, v := range documents.All() {
|
||||
v, ok := v.(wirebson.AnyDocument)
|
||||
if !ok {
|
||||
http.Error(rw, fmt.Sprintf("document %d is not a valid BSON document", i), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var doc *wirebson.Document
|
||||
|
||||
doc, err = ensureID(v)
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var insertedId any
|
||||
|
||||
insertedId, err = wirebson.ToDriver(doc.Get("_id"))
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err = documents.Replace(i, doc); err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
insertedIds = append(insertedIds, insertedId)
|
||||
}
|
||||
|
||||
msg, err := prepareRequest(
|
||||
"insert", req.Collection,
|
||||
"$db", req.Database,
|
||||
"documents", req.Documents,
|
||||
"documents", documents,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"n", float64(1),
|
||||
)
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
res := api.InsertManyResponseBody{
|
||||
InsertedIds: &insertedIds,
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
@@ -20,15 +20,16 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// InsertOne implements [ServerInterface].
|
||||
func (s *Server) InsertOne(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) InsertOne(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
@@ -37,40 +38,88 @@ func (s *Server) InsertOne(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req api.InsertOneRequestBody
|
||||
if err := decodeJSONRequest(r, &req); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
insertDoc, err := unmarshalSingleJSON(&req.Document)
|
||||
insert, err := unmarshalSingleJSON(&req.Document)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var documents any
|
||||
if insertDoc != nil {
|
||||
documents = wirebson.MustArray(insertDoc)
|
||||
insertDoc, ok := insert.(wirebson.AnyDocument)
|
||||
if !ok {
|
||||
http.Error(rw, lazyerrors.New("document must be a BSON document").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var doc *wirebson.Document
|
||||
|
||||
doc, err = ensureID(insertDoc)
|
||||
if err != nil {
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
documents := wirebson.MustArray(doc)
|
||||
|
||||
msg, err := prepareRequest(
|
||||
"insert", req.Collection,
|
||||
"$db", req.Database,
|
||||
"documents", documents,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.handler.Handle(ctx, msg)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
insertedId, err := wirebson.ToDriver(doc.Get("_id"))
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res := wirebson.MustDocument(
|
||||
"n", float64(1),
|
||||
)
|
||||
res := api.InsertOneResponseBody{
|
||||
InsertedId: &insertedId,
|
||||
}
|
||||
|
||||
s.writeJSONResponse(ctx, w, res)
|
||||
s.writeJSONResponse(ctx, rw, &res)
|
||||
}
|
||||
|
||||
// ensureID ensures that inserted document has an "_id" field.
|
||||
func ensureID(doc wirebson.AnyDocument) (*wirebson.Document, error) {
|
||||
decoded, err := doc.Decode()
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
for field := range decoded.Fields() {
|
||||
if field == "_id" {
|
||||
return decoded, nil
|
||||
}
|
||||
}
|
||||
|
||||
id, err := wirebson.FromDriver(bson.NewObjectID())
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
err = decoded.Add("_id", id)
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
return decoded, err
|
||||
}
|
||||
@@ -15,25 +15,47 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
)
|
||||
|
||||
// marshalJSON encodes wirebson.RawDocument into extended JSON.
|
||||
func marshalJSON(raw wirebson.RawDocument, jsonDst io.Writer) error {
|
||||
encoder := bson.NewEncoder(bson.NewExtJSONValueWriter(jsonDst, false, false))
|
||||
// marshalSingleJSON converts wirebson value to the standard JSON representation.
|
||||
func marshalSingleJSON(v any) (json.RawMessage, error) {
|
||||
var err error
|
||||
|
||||
if err := encoder.Encode(bson.Raw(raw)); err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
switch vt := v.(type) {
|
||||
case wirebson.RawDocument:
|
||||
v, err = vt.DecodeDeep()
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
case wirebson.RawArray:
|
||||
v, err = vt.DecodeDeep()
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
default:
|
||||
// no need to decode other types
|
||||
}
|
||||
|
||||
return nil
|
||||
bv, err := wirebson.ToDriver(v)
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
err = json.NewEncoder(&buf).Encode(bv)
|
||||
if err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
return json.RawMessage(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// unmarshalSingleJSON takes extended JSON object and unmarshals it into the wirebson composite.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 FerretDB Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
)
|
||||
|
||||
// OpenAPISpec serves the OpenAPI specification.
|
||||
func (s *Server) OpenAPISpec(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.Header().Set("Content-Length", strconv.Itoa(len(api.Spec)))
|
||||
|
||||
if _, err := rw.Write(api.Spec); err != nil {
|
||||
s.l.WarnContext(r.Context(), "Failed to write OpenAPI spec", logging.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -25,52 +25,51 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/AlekSi/lazyerrors"
|
||||
"github.com/FerretDB/wire/wirebson"
|
||||
"github.com/xdg-go/scram"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/clientconn/conninfo"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/dataapi/api"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/handler"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/handler/middleware"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// New creates a new Server.
|
||||
func New(l *slog.Logger, handler *handler.Handler) *Server {
|
||||
func New(l *slog.Logger, handler *middleware.Middleware) *Server {
|
||||
return &Server{
|
||||
l: l,
|
||||
handler: handler,
|
||||
l: l,
|
||||
m: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// Server implements services described by OpenAPI description file.
|
||||
type Server struct {
|
||||
l *slog.Logger
|
||||
handler *handler.Handler
|
||||
l *slog.Logger
|
||||
m *middleware.Middleware
|
||||
}
|
||||
|
||||
// AuthMiddleware handles SCRAM authentication based on the username and password specified in request.
|
||||
// After a successful handshake it calls the next handler.
|
||||
func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
username, password, ok := r.BasicAuth()
|
||||
|
||||
if !ok {
|
||||
writeError(w, errorNoAuthenticationSpecified, http.StatusBadRequest)
|
||||
writeError(rw, errorNoAuthenticationSpecified, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if password == "" || username == "" {
|
||||
writeError(w, errorMissingAuthenticationParameter, http.StatusBadRequest)
|
||||
writeError(rw, errorMissingAuthenticationParameter, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
client, err := scram.SHA256.NewClient(username, password, "")
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusBadRequest)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
payload, err := conv.Step("")
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusBadRequest)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -91,20 +90,23 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||
"$db", "admin",
|
||||
))
|
||||
|
||||
res, err := s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
resp := s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resDoc := must.NotFail(must.NotFail(res.OpMsg.DocumentRaw()).Decode())
|
||||
convID := resDoc.Get("conversationId").(int32)
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
payloadBytes := resDoc.Get("payload").(wirebson.Binary).B
|
||||
convID := resp.Document().Get("conversationId").(int32)
|
||||
payloadBytes := resp.Document().Get("payload").(wirebson.Binary).B
|
||||
|
||||
payload, err = conv.Step(string(payloadBytes))
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,77 +117,90 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||
"$db", "admin",
|
||||
))
|
||||
|
||||
res, err = s.handler.Handle(ctx, msg)
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
resp = s.m.Handle(ctx, msg)
|
||||
if resp == nil {
|
||||
http.Error(rw, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resDoc = must.NotFail(must.NotFail(res.OpMsg.DocumentRaw()).Decode())
|
||||
if !resDoc.Get("done").(bool) {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
if !resp.OK() {
|
||||
s.writeJSONError(ctx, rw, resp)
|
||||
return
|
||||
}
|
||||
|
||||
payloadBytes = resDoc.Get("payload").(wirebson.Binary).B
|
||||
if !resp.Document().Get("done").(bool) {
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
payloadBytes = resp.Document().Get("payload").(wirebson.Binary).B
|
||||
|
||||
if _, err = conv.Step(string(payloadBytes)); err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
http.Error(rw, lazyerrors.Error(err).Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !conv.Valid() {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// ConnInfoMiddleware returns a handler function that creates a new [*conninfo.ConnInfo],
|
||||
// calls the next handler, and closes the connection info after the request is done.
|
||||
func (s *Server) ConnInfoMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
connInfo := conninfo.New()
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ci := conninfo.New()
|
||||
|
||||
defer connInfo.Close()
|
||||
defer ci.Close()
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(conninfo.Ctx(r.Context(), connInfo)))
|
||||
next.ServeHTTP(rw, r.WithContext(conninfo.Ctx(r.Context(), ci)))
|
||||
})
|
||||
}
|
||||
|
||||
// writeJSONResponse marshals provided res document into extended JSON and
|
||||
// writes it to provided [http.ResponseWriter].
|
||||
func (s *Server) writeJSONResponse(ctx context.Context, w http.ResponseWriter, res wirebson.AnyDocument) {
|
||||
l := s.l
|
||||
func (s *Server) writeJSONResponse(ctx context.Context, rw http.ResponseWriter, res api.Response) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
resRaw, err := res.Encode()
|
||||
if err != nil {
|
||||
http.Error(w, lazyerrors.Error(err).Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var resWriter io.Writer = rw
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
var resWriter io.Writer = w
|
||||
|
||||
if l.Enabled(ctx, slog.LevelDebug) {
|
||||
if s.l.Enabled(ctx, slog.LevelDebug) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
resWriter = io.MultiWriter(w, buf)
|
||||
resWriter = io.MultiWriter(rw, buf)
|
||||
|
||||
defer func() {
|
||||
// extended JSON value writer always finish with '\n' character
|
||||
l.DebugContext(ctx, fmt.Sprintf("Results:\n%s", strings.TrimSpace(buf.String())))
|
||||
s.l.DebugContext(ctx, fmt.Sprintf("Results:\n%s", strings.TrimSpace(buf.String())))
|
||||
}()
|
||||
}
|
||||
|
||||
if err = marshalJSON(resRaw, resWriter); err != nil {
|
||||
l.ErrorContext(ctx, "marshalJSON failed", logging.Error(err))
|
||||
err := json.NewEncoder(resWriter).Encode(res)
|
||||
if err != nil {
|
||||
s.l.ErrorContext(ctx, "marshalJSON failed", logging.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4965
|
||||
func (s *Server) writeJSONError(ctx context.Context, rw http.ResponseWriter, resp *middleware.Response) {
|
||||
doc := resp.Document()
|
||||
errmsg := doc.Get("errmsg").(string)
|
||||
codeName := doc.Get("codeName").(string)
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
s.writeJSONResponse(ctx, rw, &api.Error{
|
||||
Error: errmsg,
|
||||
ErrorCode: codeName,
|
||||
})
|
||||
}
|
||||
|
||||
// prepareDocument creates a new bson document from the given pairs of
|
||||
// field names and values, which can be used as handler command msg.
|
||||
//
|
||||
@@ -257,7 +272,7 @@ func prepareRequest(pairs ...any) (*middleware.Request, error) {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
return middleware.RequestDoc(doc), nil
|
||||
return middleware.RequestDoc(doc)
|
||||
}
|
||||
|
||||
// decodeJSONRequest takes request with JSON body and decodes it into
|
||||
|
||||
Loaded 100 of 498 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user