Add FerretDB v2 (#4689)
This commit is contained in:
1 parent
046d266506
commit
e7173c3d8d
714 files changed
+35843
-66453
No files matched your search
@@ -15,3 +15,4 @@
|
||||
|
||||
# additional files for Docker images
|
||||
!build/ferretdb
|
||||
!build/postgres-documentdb
|
||||
@@ -2,20 +2,3 @@
|
||||
# Order is important; the last matching pattern takes the most precedence.
|
||||
|
||||
* @AlekSi
|
||||
|
||||
/cmd/ @FerretDB/leads
|
||||
/ferretdb/ @FerretDB/leads
|
||||
/integration/ @FerretDB/leads
|
||||
/internal/ @FerretDB/leads
|
||||
|
||||
/internal/backends/ @AlekSi
|
||||
|
||||
# @AlekSi for engineering, @ptrfarkas for marketing.
|
||||
# See also Mergify configuration.
|
||||
/website/ @AlekSi @ptrfarkas
|
||||
|
||||
/build/ @AlekSi
|
||||
/build/legacy-mongo-shell/ @FerretDB/specops
|
||||
|
||||
go.mod @AlekSi
|
||||
go.sum @AlekSi
|
||||
@@ -14,18 +14,9 @@ body:
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: FerretDB version
|
||||
label: FerretDB, PostgreSQL, and DocumentDB versions
|
||||
description: >
|
||||
What version and package of FerretDB do you use?
|
||||
Please provide an output of running FerretDB with [`--version` flag](https://docs.ferretdb.io/v1.24/configuration/flags/).
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: backend
|
||||
attributes:
|
||||
label: Backend
|
||||
description: >
|
||||
What backend (PostgreSQL, etc) and what version do you use?
|
||||
Please add the output of the `serverStatus` command.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -33,9 +24,9 @@ body:
|
||||
label: Environment
|
||||
description: |
|
||||
For example:
|
||||
- **OS**: Ubuntu 22.04 x86_64
|
||||
- **Deployment**: Docker using `ghcr.io/ferretdb/ferretdb:1.15.0` image
|
||||
- **Deployment details**: Docker version 24.0.6
|
||||
- **OS**: Ubuntu 24.04 x86_64
|
||||
- **Deployment**: Docker using `ghcr.io/ferretdb/ferretdb:2` image
|
||||
- **Deployment details**: Docker version 27.3.1
|
||||
value: |
|
||||
- OS:
|
||||
- Deployment:
|
||||
@@ -48,7 +39,7 @@ body:
|
||||
label: What did you do?
|
||||
description: >
|
||||
Include MongoDB queries or
|
||||
[pull request with a test script](https://github.com/FerretDB/FerretDB/blob/main-v1/CONTRIBUTING.md#reporting-a-bug)
|
||||
[pull request with a test script](https://github.com/FerretDB/FerretDB/blob/main/CONTRIBUTING.md#reporting-a-bug)
|
||||
if you can.
|
||||
validations:
|
||||
required: true
|
||||
@@ -65,6 +56,6 @@ body:
|
||||
attributes:
|
||||
label: What did you see instead?
|
||||
description: >
|
||||
Please include FerretDB [logs](https://docs.ferretdb.io/v1.24/configuration/observability/).
|
||||
Please include FerretDB [logs](https://docs.ferretdb.io/configuration/observability/).
|
||||
validations:
|
||||
required: true
|
||||
@@ -36,13 +36,13 @@
|
||||
4. Check:
|
||||
- https://hub.docker.com/r/ferretdb/ferretdb/tags
|
||||
- https://hub.docker.com/r/ferretdb/ferretdb-dev/tags
|
||||
- https://hub.docker.com/r/ferretdb/all-in-one/tags
|
||||
- https://hub.docker.com/r/ferretdb/ferretdb-eval/tags
|
||||
- https://github.com/FerretDB/FerretDB/pkgs/container/ferretdb
|
||||
- https://github.com/FerretDB/FerretDB/pkgs/container/ferretdb-dev
|
||||
- https://github.com/FerretDB/FerretDB/pkgs/container/all-in-one
|
||||
- https://github.com/FerretDB/FerretDB/pkgs/container/ferretdb-eval
|
||||
- https://quay.io/repository/ferretdb/ferretdb?tab=tags
|
||||
- https://quay.io/repository/ferretdb/ferretdb-dev?tab=tags
|
||||
- https://quay.io/repository/ferretdb/all-in-one?tab=tags
|
||||
- https://quay.io/repository/ferretdb/ferretdb-eval?tab=tags
|
||||
5. Close milestone in issues.
|
||||
6. Publish release on GitHub.
|
||||
7. Announce it on Slack.
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
# Apply the "not ready" label to prevent cascaded CI runs on each auto-merge.
|
||||
|
||||
version: 2
|
||||
|
||||
registries:
|
||||
ghcr:
|
||||
type: docker-registry
|
||||
url: ghcr.io
|
||||
username: ferretdb-bot
|
||||
password: ${{ secrets.PRIVATE_DEPENDABOT_TOKEN }}
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
@@ -18,6 +26,7 @@ updates:
|
||||
time: "01:42"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
registries: ["ghcr"]
|
||||
directory: "/build/ferretdb"
|
||||
labels: ["deps", "not ready"]
|
||||
assignees: [AlekSi]
|
||||
@@ -30,6 +39,19 @@ updates:
|
||||
day: "wednesday"
|
||||
time: "01:42"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/build/postgres-documentdb"
|
||||
labels: ["deps", "not ready"]
|
||||
assignees: [AlekSi]
|
||||
open-pull-requests-limit: 20
|
||||
groups:
|
||||
build-docker:
|
||||
patterns: ["*"]
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "wednesday"
|
||||
time: "01:42"
|
||||
- package-ecosystem: "docker"
|
||||
registries: ["ghcr"]
|
||||
directory: "/build/deps"
|
||||
labels: ["deps", "not ready"]
|
||||
assignees: [AlekSi]
|
||||
@@ -90,3 +112,13 @@ updates:
|
||||
interval: "weekly"
|
||||
day: "wednesday"
|
||||
time: "01:42"
|
||||
|
||||
- package-ecosystem: "gitsubmodule"
|
||||
directory: "/"
|
||||
labels: ["deps", "not ready"]
|
||||
assignees: [AlekSi]
|
||||
open-pull-requests-limit: 20
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "wednesday"
|
||||
time: "01:42"
|
||||
+5
-22
@@ -32,6 +32,9 @@ labels:
|
||||
- name: area/diag
|
||||
color: "#909BA6"
|
||||
description: Issues about diagnostic commands
|
||||
- name: area/errors
|
||||
color: "#DE231F"
|
||||
description: Issues about MongoDB-compatible error codes and messages
|
||||
- name: area/fuzz
|
||||
color: "#D4C5F9"
|
||||
description: Issues about fuzzing and smithing
|
||||
@@ -41,32 +44,12 @@ labels:
|
||||
- name: area/observability
|
||||
color: "#E04D33"
|
||||
description: Issues about metrics, logs, traces, pprof, etc
|
||||
- name: area/oplog
|
||||
color: "#09D521"
|
||||
description: Issues about OpLog and capped collections
|
||||
- name: area/params
|
||||
color: "#FBCA04"
|
||||
description: Issues about command parameters extraction
|
||||
- name: area/performance
|
||||
color: "#D02FE8"
|
||||
description: Issues about performance
|
||||
- name: area/pushdown
|
||||
color: "#83F7BA"
|
||||
description: Issues about query pushdown
|
||||
- name: area/types
|
||||
- name: area/wire
|
||||
color: "#0354F4"
|
||||
description: Issues about data types, BSON, wire protocol, etc
|
||||
|
||||
# backend
|
||||
- name: backend/pg
|
||||
color: "#305C83"
|
||||
description: Issues about PostgreSQL backend
|
||||
- name: backend/sqlite
|
||||
color: "#2C8FCA"
|
||||
description: Issues about SQLite backend
|
||||
- name: backend/hana
|
||||
color: "#2D6AB1"
|
||||
description: Issues about SAP HANA backend
|
||||
description: Issues about BSON and the wire protocol
|
||||
|
||||
# blog
|
||||
- name: blog/engineering
|
||||
|
||||
@@ -13,23 +13,21 @@ on:
|
||||
required: true
|
||||
type: number
|
||||
coveralls:
|
||||
required: true
|
||||
type: boolean
|
||||
disable_pushdown:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
postgres_dockerfile:
|
||||
test_timeout:
|
||||
required: false
|
||||
type: string
|
||||
default: "postgres"
|
||||
default: 20m
|
||||
no_xfail:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
checkout_ref:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
FERRETDB_HANA_URL:
|
||||
required: false
|
||||
|
||||
env:
|
||||
GOPATH: /home/runner/go
|
||||
@@ -51,11 +49,6 @@ jobs:
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- name: Dump github context
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJSON(github) }}
|
||||
|
||||
# TODO https://github.com/FerretDB/github-actions/issues/211
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -80,10 +73,15 @@ jobs:
|
||||
run: go generate -x
|
||||
working-directory: tools
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Start environment
|
||||
run: bin/task env-up-detach
|
||||
env:
|
||||
POSTGRES_DOCKERFILE: ${{ inputs.postgres_dockerfile }}
|
||||
|
||||
- name: Run init
|
||||
run: bin/task init
|
||||
@@ -93,42 +91,34 @@ jobs:
|
||||
|
||||
- name: >
|
||||
Run ${{ inputs.task }} tests
|
||||
(${{ inputs.shard_index }}/${{ inputs.shard_total }},
|
||||
pushdown=${{ !inputs.disable_pushdown }})
|
||||
(${{ inputs.shard_index }}/${{ inputs.shard_total }})
|
||||
run: >
|
||||
bin/task test-integration-${{ inputs.task }}
|
||||
SHARD_INDEX=${{ inputs.shard_index }}
|
||||
SHARD_TOTAL=${{ inputs.shard_total }}
|
||||
TEST_TIMEOUT=20m
|
||||
DISABLE_PUSHDOWN=${{ inputs.disable_pushdown }}
|
||||
TEST_TIMEOUT=${{ inputs.test_timeout }}
|
||||
NO_XFAIL=${{ inputs.no_xfail }}
|
||||
env:
|
||||
GOFLAGS: ${{ runner.debug == '1' && '-v' || '' }}
|
||||
FERRETDB_HANA_URL: ${{ secrets.FERRETDB_HANA_URL }}
|
||||
ENVTOOL_DEBUG: ${{ runner.debug == '1' && 'true' || 'false' }}
|
||||
|
||||
# The token is not required but should make uploads more stable.
|
||||
# If secrets are unavailable (for example, for a pull request from a fork), it fallbacks to the tokenless uploads.
|
||||
#
|
||||
# Unfortunately, it seems that tokenless uploads fail too often.
|
||||
# See https://github.com/codecov/codecov-action/issues/837.
|
||||
#
|
||||
# We also can't use ${{ vars.CODECOV_TOKEN }}: https://github.com/orgs/community/discussions/44322
|
||||
- name: Upload coverage information to codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: 3c5cbd1b-5110-4302-ad5a-080390ca731a
|
||||
files: ./integration/integration-${{ inputs.task }}.txt
|
||||
flags: integration,${{ inputs.task }}-${{ inputs.shard_index }},filter-${{ !inputs.disable_pushdown }}
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
- 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/
|
||||
|
||||
- name: Upload coverage information to coveralls
|
||||
if: always() && inputs.coveralls
|
||||
uses: coverallsapp/github-action@v2
|
||||
- name: Compress logs before upload
|
||||
if: failure()
|
||||
run: zip -r -q -9 logs.zip /tmp/logs
|
||||
|
||||
- name: Upload logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
file: ./integration/integration-${{ inputs.task }}.txt
|
||||
flag-name: integration-${{ inputs.task }}-${{ inputs.shard_index }}-filter-${{ !inputs.disable_pushdown }}
|
||||
parallel: true
|
||||
name: logs-${{ inputs.task }}-${{ inputs.shard_index }}-${{ inputs.shard_total }}
|
||||
path: logs.zip
|
||||
retention-days: 3
|
||||
|
||||
# we don't want them on CI
|
||||
- name: Clean test and fuzz caches
|
||||
@@ -136,6 +126,8 @@ jobs:
|
||||
run: go clean -testcache -fuzzcache
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
@@ -63,7 +63,11 @@ env:
|
||||
jobs:
|
||||
conform-pr:
|
||||
name: Conform PR
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 5
|
||||
|
||||
# No `trust` label check because we don't checkout PR's code.
|
||||
|
||||
@@ -28,7 +28,11 @@ concurrency:
|
||||
jobs:
|
||||
contexts:
|
||||
name: Contexts
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 5
|
||||
|
||||
# comment out to enable this job
|
||||
@@ -43,7 +47,9 @@ jobs:
|
||||
lfs: false # LFS is used only by website
|
||||
|
||||
- name: Debug git status
|
||||
run: git status
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
|
||||
- name: Dump github context
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
|
||||
@@ -32,7 +32,11 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
name: Build docs
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
@@ -49,6 +53,7 @@ jobs:
|
||||
# See https://github.com/ppremk/lfs-warning/issues/147.
|
||||
# But the real issue will be right above that message: a list of files that should be added using Git LFS.
|
||||
- name: Check LFS status
|
||||
if: false # disabled until release
|
||||
uses: ppremk/lfs-warning@v3.3
|
||||
with:
|
||||
filesizelimit: 102400b
|
||||
@@ -67,6 +72,13 @@ jobs:
|
||||
run: go generate -x
|
||||
working-directory: tools
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build tools
|
||||
run: bin/task env-pull
|
||||
|
||||
@@ -96,7 +108,9 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
|
||||
@@ -107,7 +121,8 @@ jobs:
|
||||
|
||||
# exclude pull_request and schedule
|
||||
needs: build
|
||||
if: github.event_name == 'push'
|
||||
# if: github.event_name == 'push'
|
||||
if: false # disabled until release
|
||||
|
||||
permissions:
|
||||
pages: write
|
||||
|
||||
@@ -31,7 +31,11 @@ env:
|
||||
jobs:
|
||||
format:
|
||||
name: Format files
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
@@ -60,6 +64,8 @@ jobs:
|
||||
run: bin/task fmt-yaml
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
@@ -1,62 +0,0 @@
|
||||
---
|
||||
# Integration tests for extra configuration.
|
||||
|
||||
name: Go Extra
|
||||
on:
|
||||
schedule:
|
||||
- cron: "12 1 * * *" # after Go workflow to reuse "Setup Go" cache
|
||||
|
||||
env:
|
||||
GOPATH: /home/runner/go
|
||||
GOCACHE: /home/runner/go/cache
|
||||
GOLANGCI_LINT_CACHE: /home/runner/go/cache/lint
|
||||
GOMODCACHE: /home/runner/go/mod
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
# job name must be unique; make it unique and nice
|
||||
name: >
|
||||
${{ matrix.task }} ${{ matrix.postgres_dockerfile }} ${{ matrix.shard_index }}/${{ matrix.shard_total }}
|
||||
(pushdown=${{ matrix.disable_pushdown }})
|
||||
|
||||
# To avoid conflict with go.yml.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-integration-${{ matrix.task }}-${{ matrix.postgres_dockerfile }}-${{ matrix.shard_index }}-${{ matrix.disable_pushdown }}-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [postgresql, sqlite, mysql]
|
||||
shard_index: [1, 2, 3]
|
||||
shard_total: [3]
|
||||
disable_pushdown: [false, true]
|
||||
include:
|
||||
- task: postgresql
|
||||
shard_index: 1
|
||||
shard_total: 3
|
||||
disable_pushdown: false
|
||||
postgres_dockerfile: citus
|
||||
- task: postgresql
|
||||
shard_index: 2
|
||||
shard_total: 3
|
||||
disable_pushdown: false
|
||||
postgres_dockerfile: citus
|
||||
- task: postgresql
|
||||
shard_index: 3
|
||||
shard_total: 3
|
||||
disable_pushdown: false
|
||||
postgres_dockerfile: citus
|
||||
|
||||
# Do not submit to coveralls because it can't handle parallel workflows:
|
||||
# https://github.com/lemurheavy/coveralls-public/issues/1636#issuecomment-1529460515
|
||||
uses: ./.github/workflows/_integration.yml
|
||||
with:
|
||||
task: ${{ matrix.task }}
|
||||
shard_index: ${{ matrix.shard_index }}
|
||||
shard_total: ${{ matrix.shard_total }}
|
||||
disable_pushdown: ${{ matrix.disable_pushdown }}
|
||||
postgres_dockerfile: ${{ matrix.postgres_dockerfile }}
|
||||
coveralls: false
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
# Integration tests that need access to secrets.
|
||||
|
||||
name: Go Trusted
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
# not for "labeled" to prevent two builds for "labeled" and "unlabeled" when labels are changed
|
||||
- unlabeled # if GitHub Actions stuck, add and remove "not ready" label to force rebuild
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- main-*
|
||||
- releases/*
|
||||
schedule:
|
||||
- cron: "12 1 * * *" # after Go workflow to reuse "Setup Go" cache
|
||||
|
||||
env:
|
||||
GOPATH: /home/runner/go
|
||||
GOCACHE: /home/runner/go/cache
|
||||
GOLANGCI_LINT_CACHE: /home/runner/go/cache/lint
|
||||
GOMODCACHE: /home/runner/go/mod
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
# job name must be unique; make it unique and nice
|
||||
name: ${{ matrix.task }} ${{ matrix.shard_index }}/${{ matrix.shard_total }}
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push
|
||||
# to save some resources.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-integration-${{ matrix.task }}-${{ matrix.shard_index }}-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
if: >
|
||||
github.event_name != 'pull_request_target' ||
|
||||
(
|
||||
contains(github.event.pull_request.labels.*.name, 'trust') &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
)
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [hana]
|
||||
shard_index: [1]
|
||||
shard_total: [1]
|
||||
|
||||
# Do not submit to coveralls because it can't handle parallel workflows:
|
||||
# https://github.com/lemurheavy/coveralls-public/issues/1636#issuecomment-1529460515
|
||||
uses: ./.github/workflows/_integration.yml
|
||||
with:
|
||||
task: ${{ matrix.task }}
|
||||
shard_index: ${{ matrix.shard_index }}
|
||||
shard_total: ${{ matrix.shard_total }}
|
||||
checkout_ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || ''}}
|
||||
coveralls: false
|
||||
secrets:
|
||||
FERRETDB_HANA_URL: ${{ secrets.FERRETDB_HANA_URL }}
|
||||
+76
-38
@@ -26,7 +26,11 @@ env:
|
||||
jobs:
|
||||
short-test:
|
||||
name: Short test
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 15
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push.
|
||||
@@ -68,13 +72,19 @@ jobs:
|
||||
run: go clean -testcache -fuzzcache
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 15
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push.
|
||||
@@ -82,7 +92,9 @@ jobs:
|
||||
group: ${{ github.workflow }}-test-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
# skip this job for now
|
||||
if: false
|
||||
# if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
|
||||
steps:
|
||||
# TODO https://github.com/FerretDB/github-actions/issues/211
|
||||
@@ -101,58 +113,45 @@ jobs:
|
||||
run: go generate -x
|
||||
working-directory: tools
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Start environment
|
||||
run: bin/task env-up-detach
|
||||
|
||||
- name: Run init
|
||||
run: bin/task init
|
||||
|
||||
- name: Wait for and setup environment
|
||||
run: bin/task env-setup
|
||||
|
||||
- name: Regenerate and reformat
|
||||
run: bin/task gen
|
||||
env:
|
||||
DOCUMENTDB_GEN_URL: postgres://username:password@127.0.0.1:5432/postgres
|
||||
|
||||
- name: Build bin/ferretdb
|
||||
run: bin/task build-host
|
||||
|
||||
- name: Wait for and setup environment
|
||||
run: bin/task env-setup
|
||||
|
||||
- name: Run unit tests
|
||||
run: bin/task test-unit TEST_TIMEOUT=10m
|
||||
env:
|
||||
GOFLAGS: ${{ runner.debug == '1' && '-v' || '' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for tools
|
||||
|
||||
# The token is not required but should make uploads more stable.
|
||||
# If secrets are unavailable (for example, for a pull request from a fork), it fallbacks to the tokenless uploads.
|
||||
#
|
||||
# Unfortunately, it seems that tokenless uploads fail too often.
|
||||
# See https://github.com/codecov/codecov-action/issues/837.
|
||||
#
|
||||
# We also can't use ${{ vars.CODECOV_TOKEN }}: https://github.com/orgs/community/discussions/44322
|
||||
- name: Upload coverage information to codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: 3c5cbd1b-5110-4302-ad5a-080390ca731a
|
||||
files: ./cover.txt
|
||||
flags: unit
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
|
||||
- name: Upload coverage information to coveralls
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
file: ./cover.txt
|
||||
flag-name: unit
|
||||
parallel: true
|
||||
|
||||
# we don't want them on CI
|
||||
- name: Clean test and fuzz caches
|
||||
if: always()
|
||||
run: go clean -testcache -fuzzcache
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
|
||||
@@ -171,7 +170,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
task: [postgresql, sqlite]
|
||||
task: [postgresql]
|
||||
shard_index: [1, 2, 3, 4, 5]
|
||||
shard_total: [5]
|
||||
include:
|
||||
@@ -184,23 +183,47 @@ jobs:
|
||||
shard_total: ${{ matrix.shard_total }}
|
||||
coveralls: true
|
||||
|
||||
integration-no-xfail:
|
||||
name: postgresql no-xfail
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push
|
||||
# to save some resources.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-integration-no-xfail-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
|
||||
uses: ./.github/workflows/_integration.yml
|
||||
with:
|
||||
task: postgresql
|
||||
shard_index: 1
|
||||
shard_total: 1
|
||||
test_timeout: 5m
|
||||
no_xfail: true
|
||||
|
||||
# https://github.com/lemurheavy/coveralls-public/issues/1636
|
||||
submit-coveralls:
|
||||
name: Submit final coveralls report
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
needs: [test, integration]
|
||||
if: always() && (github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready'))
|
||||
|
||||
steps:
|
||||
- name: Submit coveralls
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
parallel-finished: true
|
||||
- name: Do nothing
|
||||
run: echo 'nothing'
|
||||
|
||||
env-data:
|
||||
name: env-data
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push.
|
||||
@@ -227,6 +250,13 @@ jobs:
|
||||
run: go generate -x
|
||||
working-directory: tools
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Start environment
|
||||
run: bin/task env-up-detach
|
||||
|
||||
@@ -249,7 +279,11 @@ jobs:
|
||||
|
||||
golangci-lint:
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
# Do not run this job in parallel for any PR change or branch push.
|
||||
@@ -259,6 +293,10 @@ jobs:
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
# TODO https://github.com/FerretDB/github-actions/issues/211
|
||||
- name: Checkout code
|
||||
|
||||
@@ -68,6 +68,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # for `git describe` to work
|
||||
lfs: false # LFS is used only by website
|
||||
ssh-key: ${{ secrets.FERRETDB_DOCUMENTDB_DEPLOY_KEY }}
|
||||
|
||||
# TODO https://github.com/FerretDB/github-actions/issues/211
|
||||
- name: Checkout pull request code
|
||||
@@ -77,6 +78,7 @@ jobs:
|
||||
fetch-depth: 0 # for `git describe` to work
|
||||
lfs: false # LFS is used only by website
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
ssh-key: ${{ secrets.FERRETDB_DOCUMENTDB_DEPLOY_KEY }}
|
||||
|
||||
# for version.txt on push tags; see https://github.com/actions/checkout/issues/290
|
||||
- name: Fetch annotated tags
|
||||
@@ -116,12 +118,6 @@ jobs:
|
||||
id: tag
|
||||
run: bin/definedockertag
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ferretdbbot
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -129,16 +125,9 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Quay.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ferretdbbot
|
||||
password: ${{ secrets.QUAY_TOKEN }}
|
||||
|
||||
- name: Build and push all-in-one Docker images ${{ steps.tag.outputs.all_in_one_images }}
|
||||
if: steps.tag.outputs.all_in_one_images != ''
|
||||
run: bin/task docker-all-in-one-push DOCKER_IMAGES=${{ steps.tag.outputs.all_in_one_images }}
|
||||
- name: Build and push evaluation Docker images ${{ steps.tag.outputs.evaluation_images }}
|
||||
if: steps.tag.outputs.evaluation_images != ''
|
||||
run: bin/task docker-evaluation-push DOCKER_IMAGES=${{ steps.tag.outputs.evaluation_images }}
|
||||
|
||||
- name: Build and push development Docker images ${{ steps.tag.outputs.development_images }}
|
||||
if: steps.tag.outputs.development_images != ''
|
||||
@@ -190,6 +179,8 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
@@ -31,7 +31,11 @@ env:
|
||||
jobs:
|
||||
security-scan:
|
||||
name: Security scan
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: JS
|
||||
name: Test scripts
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
@@ -9,6 +9,7 @@ on:
|
||||
- synchronize
|
||||
paths:
|
||||
- build/legacy-mongo-shell/*.js
|
||||
- build/testscripts/*.sql
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -31,9 +32,13 @@ env:
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
testjs:
|
||||
name: Test script
|
||||
runs-on: ubuntu-22.04
|
||||
run:
|
||||
name: Run test scripts
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'not ready')
|
||||
@@ -49,12 +54,19 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: FerretDB/github-actions/setup-go@main
|
||||
with:
|
||||
cache-key: testjs
|
||||
cache-key: testscripts
|
||||
|
||||
- name: Install Task
|
||||
run: go generate -x
|
||||
working-directory: tools
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Start environment
|
||||
run: bin/task env-up-detach
|
||||
|
||||
@@ -67,20 +79,26 @@ jobs:
|
||||
- name: Wait for and setup environment
|
||||
run: bin/task env-setup
|
||||
|
||||
- name: Start FerretDB in the background
|
||||
- name: Start FerretDB (no auth) in the background
|
||||
run: >
|
||||
bin/ferretdb
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--proxy-addr=127.0.0.1:37017
|
||||
--no-auth
|
||||
--mode=diff-normal
|
||||
--handler=pg
|
||||
--postgresql-url='postgres://username@127.0.0.1:5432/ferretdb'
|
||||
--postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
|
||||
2> ferretdb.log &
|
||||
|
||||
- name: Run testjs for MongoDB
|
||||
run: bin/task testjs TESTJS_PORT=47017
|
||||
- name: Run testsql for PostgreSQL
|
||||
if: always()
|
||||
run: bin/task testsql
|
||||
|
||||
- name: Run testjs for FerretDB
|
||||
- name: Run testjs for MongoDB (no auth)
|
||||
if: always()
|
||||
run: bin/task testjs TESTJS_PORT=37017
|
||||
|
||||
- name: Run testjs for FerretDB (no auth)
|
||||
if: always()
|
||||
run: bin/task testjs
|
||||
|
||||
- name: Compress FerretDB log before upload
|
||||
@@ -108,6 +126,8 @@ jobs:
|
||||
run: go clean -testcache -fuzzcache
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
@@ -26,7 +26,11 @@ env:
|
||||
jobs:
|
||||
blog-preview:
|
||||
name: Preview blog
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: >
|
||||
@@ -77,7 +81,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for checkdocs
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
|
||||
@@ -93,7 +99,11 @@ jobs:
|
||||
|
||||
docs-preview:
|
||||
name: Preview docs
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
# https://www.ubicloud.com/docs/about/pricing#github-actions
|
||||
# https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#per-minute-rates-for-larger-runners
|
||||
runs-on: ubicloud-standard-2
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
if: >
|
||||
@@ -148,7 +158,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for checkdocs
|
||||
|
||||
- name: Check dirty
|
||||
if: always()
|
||||
run: |
|
||||
git status --untracked-files --ignored
|
||||
git status
|
||||
git diff --exit-code
|
||||
|
||||
|
||||
+4
-2
@@ -1,12 +1,14 @@
|
||||
# Please use global .gitignore file for .vscode/, .idea/, go.work, .envrc, etc.
|
||||
# This file is used only for repository-specific patterns.
|
||||
# Please use global .gitignore file for .DS_Store, .idea/, go.work, .envrc, etc.
|
||||
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer
|
||||
|
||||
docker-compose.override.yml
|
||||
|
||||
state.json
|
||||
telemetry.json
|
||||
|
||||
*.out
|
||||
*.sqlite*
|
||||
*.tmp
|
||||
*.txt
|
||||
!website/static/*.txt
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "documentdb"]
|
||||
path = build/postgres-documentdb/documentdb
|
||||
url = https://github.com/FerretDB/documentdb.git
|
||||
branch = main
|
||||
+6
-11
@@ -5,6 +5,7 @@
|
||||
run:
|
||||
timeout: 3m
|
||||
|
||||
# bin/golangci-lint linters -c .golangci-new.yml
|
||||
linters-settings:
|
||||
asasalint:
|
||||
exclude:
|
||||
@@ -18,8 +19,7 @@ linters-settings:
|
||||
check-blank: false
|
||||
disable-default-exclusions: false
|
||||
exclude-functions:
|
||||
- (*database/sql.Rows).Close
|
||||
- (*github.com/FerretDB/FerretDB/internal/util/fsql.Rows).Close
|
||||
- (*github.com/jackc/pgx/v5.Conn).Close
|
||||
errorlint:
|
||||
# see caveats at https://github.com/polyfloyd/go-errorlint#fmterrorf-wrapping-verb
|
||||
errorf: false
|
||||
@@ -41,14 +41,6 @@ linters-settings:
|
||||
- name: package-comments
|
||||
staticcheck:
|
||||
checks: ["all"]
|
||||
tagalign:
|
||||
sort: true
|
||||
order:
|
||||
- name
|
||||
- cmd
|
||||
- arg
|
||||
- default
|
||||
- help
|
||||
wsl:
|
||||
# https://github.com/bombsimon/wsl/blob/master/doc/configuration.md
|
||||
strict-append: false
|
||||
@@ -86,6 +78,8 @@ linters:
|
||||
- misspell
|
||||
- nolintlint
|
||||
- sloglint
|
||||
- tagalign
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
@@ -123,6 +117,7 @@ linters:
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- grouper
|
||||
- iface
|
||||
- inamedparam
|
||||
- interfacebloat
|
||||
- intrange
|
||||
@@ -147,6 +142,7 @@ linters:
|
||||
- promlinter
|
||||
- protogetter
|
||||
- reassign
|
||||
- recvcheck
|
||||
- rowserrcheck
|
||||
- sloglint
|
||||
- spancheck
|
||||
@@ -160,7 +156,6 @@ linters:
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- usestdlibvars
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
|
||||
+63
-52
@@ -16,8 +16,8 @@ linters-settings:
|
||||
desc: use `context` package instead
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: use `slices` package instead
|
||||
- pkg: maps
|
||||
desc: use `golang.org/x/exp/maps` package instead
|
||||
# - pkg: golang.org/x/exp/maps
|
||||
# desc: use `maps` package instead
|
||||
- pkg: github.com/jackc/pgconn
|
||||
desc: use `github.com/jackc/pgx/v5/pgconn` package instead
|
||||
- pkg: github.com/jackc/pgproto3
|
||||
@@ -28,48 +28,42 @@ linters-settings:
|
||||
desc: use `github.com/jackc/pgx/v5` package instead
|
||||
- pkg: github.com/jackc/pgx/v4
|
||||
desc: use `github.com/jackc/pgx/v5` package instead
|
||||
|
||||
- pkg: github.com/uber-go/zap
|
||||
desc: use `log/slog` package instead
|
||||
#- pkg: github.com/xdg-go/scram
|
||||
# desc: use `github.com/FerretDB/FerretDB/v2/internal/util/scram` package instead
|
||||
ferretdb-in-tools:
|
||||
files:
|
||||
- "**/tools/**/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/
|
||||
- pkg: github.com/FerretDB/FerretDB/v2/internal/
|
||||
desc: tools should not import FerretDB code
|
||||
|
||||
types:
|
||||
files:
|
||||
- "**/internal/util/logging/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/types
|
||||
sjson:
|
||||
files:
|
||||
- $all
|
||||
- "!**/internal/backends/sqlite/*.go"
|
||||
- "!**/internal/backends/postgresql/*.go"
|
||||
- "!**/internal/backends/postgresql/metadata/*.go"
|
||||
- "!**/internal/backends/mysql/*.go"
|
||||
- "!**/internal/backends/mysql/metadata/*.go"
|
||||
- "!**/internal/backends/hana/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/handler/sjson
|
||||
common:
|
||||
files:
|
||||
- "**/internal/backends/**.go"
|
||||
- "**/internal/clientconn/cursor/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/handler/common
|
||||
handlererrors:
|
||||
wirebson:
|
||||
files:
|
||||
- $all
|
||||
- "!**/cmd/envtool/*.go"
|
||||
- "!**/internal/clientconn/*.go"
|
||||
- "!**/internal/dataapi/server/*.go"
|
||||
- "!**/internal/documentdb/**.go"
|
||||
- "!**/internal/driver/*.go"
|
||||
- "!**/internal/handler/**.go"
|
||||
- "!**/internal/mongoerrors/*.go"
|
||||
- "!**/internal/util/logging/*.go"
|
||||
- "!**/internal/util/scram/*.go"
|
||||
- "!**/internal/util/testutil/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/handler/handlererrors
|
||||
backends:
|
||||
- pkg: github.com/FerretDB/wire/wirebson
|
||||
documentdb:
|
||||
files:
|
||||
- "**/internal/clientconn/cursor/*.go"
|
||||
- "**/internal/documentdb/genwrap/*.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/backends
|
||||
- pkg: github.com/FerretDB/FerretDB/v2/internal/documentdb
|
||||
cursor:
|
||||
files:
|
||||
- $all
|
||||
- "!**/internal/documentdb/**.go"
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/v2/internal/documentdb/cursor
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
forbidigo:
|
||||
@@ -81,35 +75,35 @@ linters-settings:
|
||||
# integration tests use a different configuration file
|
||||
- p: ^\Qbson.E\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson\E$
|
||||
msg: Use `*types.Document` or `*types.Array` instead.
|
||||
msg: Use `*bson.Document` or `*bson.Array` instead.
|
||||
- p: ^\Qprimitive.E\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson/primitive\E$
|
||||
msg: Use `*types.Document` or `*types.Array` instead.
|
||||
msg: Use `*bson.Document` or `*bson.Array` instead.
|
||||
- p: ^\Qbson.D\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson\E$
|
||||
msg: Use `*types.Document` instead.
|
||||
msg: Use `*bson.Document` instead.
|
||||
- p: ^\Qprimitive.D\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson/primitive\E$
|
||||
msg: Use `*types.Document` instead.
|
||||
msg: Use `*bson.Document` instead.
|
||||
- p: ^\Qbson.M\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson\E$
|
||||
msg: Use `*types.Document` instead.
|
||||
msg: Use `*bson.Document` instead.
|
||||
- p: ^\Qprimitive.M\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson/primitive\E$
|
||||
msg: Use `*types.Document` instead.
|
||||
msg: Use `*bson.Document` instead.
|
||||
- p: ^\Qbson.A\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson\E$
|
||||
msg: Use `*types.Array` instead.
|
||||
msg: Use `*bson.Array` instead.
|
||||
- p: ^\Qprimitive.A\E$
|
||||
pkg: ^\Qgo.mongodb.org/mongo-driver/bson/primitive\E$
|
||||
msg: Use `*types.Array` instead.
|
||||
msg: Use `*bson.Array` instead.
|
||||
exclude-godoc-examples: true
|
||||
analyze-types: true
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/FerretDB/FerretDB)
|
||||
- prefix(github.com/FerretDB/FerretDB) # both /v2 and /tools
|
||||
goconst:
|
||||
min-occurrences: 5
|
||||
ignore-tests: true
|
||||
@@ -123,6 +117,8 @@ linters-settings:
|
||||
- BUG
|
||||
- FIXME
|
||||
- HACK
|
||||
- lala
|
||||
- lalala
|
||||
goheader:
|
||||
# Please do not update it, including bumping a year.
|
||||
template: |-
|
||||
@@ -140,10 +136,12 @@ linters-settings:
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
goimports:
|
||||
local-prefixes: github.com/FerretDB/FerretDB
|
||||
local-prefixes: github.com/FerretDB/FerretDB/v2
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules: []
|
||||
modules:
|
||||
- github.com/FerretDB/FerretDB:
|
||||
reason: "Use `github.com/FerretDB/FerretDB/v2/...` instead."
|
||||
gosimple:
|
||||
checks: ["all"]
|
||||
govet:
|
||||
@@ -153,15 +151,12 @@ linters-settings:
|
||||
- nilness
|
||||
- shadow
|
||||
importas:
|
||||
no-unaliased: false
|
||||
no-extra-aliases: true
|
||||
# https://github.com/julz/importas/issues/15
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
- pkg: github.com/prometheus/client_model/go
|
||||
alias: dto
|
||||
- pkg: modernc.org/sqlite
|
||||
alias: sqlite3
|
||||
- pkg: modernc.org/sqlite/lib
|
||||
alias: sqlite3lib
|
||||
- pkg: go.opentelemetry.io/otel/attribute
|
||||
alias: otelattribute
|
||||
- pkg: go.opentelemetry.io/otel/codes
|
||||
@@ -174,8 +169,6 @@ linters-settings:
|
||||
alias: otelsdktrace
|
||||
- pkg: go.opentelemetry.io/otel/semconv/v1.21.0
|
||||
alias: otelsemconv
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/backends/mysql/metadata/pool
|
||||
alias: mysqlpool
|
||||
# ineffassign
|
||||
lll:
|
||||
line-length: 130
|
||||
@@ -211,7 +204,23 @@ linters-settings:
|
||||
checks:
|
||||
- all
|
||||
- -SA1019 # ignore deprecation errors in existing code; new code is checked by the other configuration
|
||||
# unused
|
||||
tagalign:
|
||||
sort: true
|
||||
order:
|
||||
- name
|
||||
- cmd
|
||||
- arg
|
||||
- default
|
||||
- help
|
||||
unparam:
|
||||
check-exported: false
|
||||
unused:
|
||||
field-writes-are-uses: false
|
||||
post-statements-are-reads: false
|
||||
exported-fields-are-used: false
|
||||
parameters-are-used: true # it should be false, TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/823
|
||||
local-variables-are-used: false
|
||||
generated-is-used: false
|
||||
whitespace:
|
||||
multi-if: false
|
||||
multi-func: false
|
||||
@@ -241,6 +250,8 @@ linters:
|
||||
- revive
|
||||
- sloglint
|
||||
- staticcheck
|
||||
- tagalign
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# We can't have it in build/.prettierignore due to https://github.com/prettier/prettier/issues/15428
|
||||
# Textlint uses the same format, so we re-use the file.
|
||||
build/postgres-documentdb/documentdb/**
|
||||
internal/dataapi/api/openapi.json
|
||||
website/static/codapi/snippet.js
|
||||
+3
-1704
File diff suppressed because it is too large.
Load diff
+65
-47
@@ -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.22 or later on the host.
|
||||
You will need Go 1.23 or later on the host.
|
||||
If your package manager doesn't provide it yet,
|
||||
please install it from [go.dev](https://go.dev/dl/).
|
||||
|
||||
@@ -106,8 +106,6 @@ To help us accurately identify the cause, we encourage you to include a pull req
|
||||
Please write the test script in [build/legacy-mongo-shell/test.js](build/legacy-mongo-shell/test.js).
|
||||
You can find an overview of the available assertions [here](build/legacy-mongo-shell/README.md).
|
||||
Use these assertions to validate your test's assumptions and invariants.
|
||||
You can also find an example of how to prepare a test script in
|
||||
[build/legacy-mongo-shell/test.example.js](build/legacy-mongo-shell/test.example.js).
|
||||
|
||||
With `task` installed (see above), you may test your script using following steps:
|
||||
|
||||
@@ -125,12 +123,20 @@ With `task` installed (see above), you may do the following:
|
||||
|
||||
1. Start the development environment with `task env-up`.
|
||||
2. Run all tests with `task test` (see [below](#running-tests)).
|
||||
3. Start FerretDB with `task run`, `task run-sqlite` or `task run-proxy`.
|
||||
(See [Operation modes](https://docs.ferretdb.io/v1.24/configuration/operation-modes/) page in our documentation.)
|
||||
3. Start FerretDB with `task run`.
|
||||
The development environment uses `diff-normal` mode as the default mode.
|
||||
Set preferred mode such as `task run MODE=diff-proxy`.
|
||||
(See [Operation modes](https://docs.ferretdb.io/configuration/operation-modes/) page in our documentation.)
|
||||
4. Fill collections in the `test` database with data for experiments with `task env-data`.
|
||||
5. Run `mongosh` with `task mongosh`.
|
||||
See what collections were created by the previous command with `show collections`.
|
||||
|
||||
#### Commands for authenticated connection
|
||||
|
||||
1. Start the development environment with `task env-up`.
|
||||
2. Start FerretDB with `task run-secure MODE=normal` or `task run-secure MODE=proxy`.
|
||||
3. Run `task mongosh-secure` to establish an authenticated connection.
|
||||
|
||||
### Code overview
|
||||
|
||||
The directory `cmd` provides commands implementation.
|
||||
@@ -141,19 +147,19 @@ They are installed into `bin/` by `cd tools; go generate -x`.
|
||||
|
||||
The `internal` subpackages contain most of the FerretDB code:
|
||||
|
||||
- `types` package provides Go types matching BSON types that don't have built-in Go equivalents:
|
||||
we use `int32` for BSON's int32, but `types.ObjectID` for BSON's ObjectId.
|
||||
- `bson` package provides converters from/to BSON for built-in and `types` types.
|
||||
- `wire` package provides wire protocol implementation.
|
||||
- `clientconn` package provides client connection implementation.
|
||||
It accepts client connections, reads `wire`/`bson` protocol messages, and passes them to `handler`.
|
||||
Responses are then converted to `wire`/`bson` messages and sent back to the client.
|
||||
- `handler` contains implementations of command handlers.
|
||||
They use `types` and `wire` packages, but `bson` package details are hidden.
|
||||
- `handler/sjson` provides converters from/to SJSON for built-in and `types` types.
|
||||
SJSON adds some extensions to JSON for keeping object keys in order,
|
||||
preserving BSON type information in the values themselves, etc.
|
||||
It is used by some backends.
|
||||
- `handler` provides implementations of command handlers.
|
||||
The logic of commands like `find` and `aggregate` is there.
|
||||
- `dataapi` provides an Data API wrapper for `handler`.
|
||||
It allows FerretDB to be used over HTTP instead of MongoDB wire protocol.
|
||||
- `clientconn` provides wire protocol server implementation.
|
||||
It accepts client connections, reads wire protocol messages, and passes them to `handler`.
|
||||
Responses are then converted and sent back to the client.
|
||||
- `documentdb` provides DocumentDB extension integration.
|
||||
- `mongoerrors` provides MongoDB-compatible error types and codes.
|
||||
They are used by `handler` and `documentdb`.
|
||||
|
||||
The wire protocol implementation and BSON handling code were extracted into a separate repository:
|
||||
https://github.com/FerretDB/wire
|
||||
|
||||
#### Running tests
|
||||
|
||||
@@ -169,13 +175,13 @@ They use the Go MongoDB driver like a regular user application.
|
||||
They could test any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP or TLS port
|
||||
or Unix domain socket.
|
||||
They also could test in-process FerretDB instances
|
||||
(meaning that integration tests start and stop them themselves) with a given backend.
|
||||
(meaning that integration tests start and stop them themselves).
|
||||
Finally, some integration tests (so-called compatibility or "compat" tests) connect to two systems
|
||||
("target" for FerretDB and "compat" for MongoDB) at the same time,
|
||||
send the same queries to both, and compare results.
|
||||
You can run them with:
|
||||
|
||||
- `task test-integration-postgresql` for in-process FerretDB with PostgreSQL backend and MongoDB;
|
||||
- `task test-integration-postgresql` for in-process FerretDB and MongoDB;
|
||||
- `task test-integration-mongodb` for MongoDB only, skipping compat tests;
|
||||
- or `task test-integration` to run all in parallel.
|
||||
|
||||
@@ -185,7 +191,7 @@ If tests fail and the output is too confusing, try running them sequentially by
|
||||
You can also run `task -C 1` to limit the number of concurrent tasks, which is useful for debugging.
|
||||
|
||||
To run a subset of integration tests and test cases, you may use Task variable `TEST_RUN`.
|
||||
For example, to run all tests related to the `getMore` command implementation for in-process FerretDB with PostgreSQL backend
|
||||
For example, to run all tests for the `getMore` command with in-process FerretDB
|
||||
you may use `task test-integration-postgresql TEST_RUN='(?i)GetMore'`.
|
||||
|
||||
Finally, since all tests just run `go test` with various arguments and flags under the hood
|
||||
@@ -194,12 +200,10 @@ you may also use all standard `go` tool facilities,
|
||||
including [`GOFLAGS` environment variable](https://pkg.go.dev/cmd/go#hdr-Environment_variables).
|
||||
For example:
|
||||
|
||||
- to run all tests related to the `getMore` command implementation in-process FerretDB with PostgreSQL backend
|
||||
with all subtests running sequentially,
|
||||
you may use `env GOFLAGS='-parallel=1' task test-integration-postgresql TEST_RUN='(?i)GetMore'`;
|
||||
- to run all tests for in-process FerretDB with SQLite backend
|
||||
with [Go execution tracer](https://pkg.go.dev/runtime/trace) enabled,
|
||||
you may use `env GOFLAGS='-trace=trace.out' task test-integration-sqlite`.
|
||||
- to run all tests for the `getMore` command sequentially,
|
||||
you may use `env GOFLAGS='-parallel=1 -p=1' task test-integration-postgresql TEST_RUN='(?i)GetMore'`;
|
||||
- to run all tests with MongoDB with [Go execution tracer](https://pkg.go.dev/runtime/trace) enabled
|
||||
you may use `env GOFLAGS='-trace=trace.out' task test-integration-mongodb`.
|
||||
|
||||
<!-- textlint-disable one-sentence-per-line -->
|
||||
|
||||
@@ -239,10 +243,10 @@ and some other pages such as [Spelling](https://go.dev/wiki/Spelling).
|
||||
Some of our idiosyncrasies are documented below.
|
||||
|
||||
1. We use type switches over BSON types in many places in our code.
|
||||
The order of `case`s follows this order: https://pkg.go.dev/github.com/FerretDB/FerretDB/internal/types#hdr-Mapping
|
||||
The order of `case`s follows this order: https://pkg.go.dev/github.com/FerretDB/wire/wirebson#hdr-Types
|
||||
It may seem random, but it is only pseudo-random and follows BSON spec: https://bsonspec.org/spec.html
|
||||
2. We generally pass and return `struct`s by pointers.
|
||||
There are some exceptions like `types.Path` that have value semantics, but when in doubt – use pointers.
|
||||
There are some exceptions like `time.Time` that have value semantics, but when in doubt – use pointers.
|
||||
|
||||
#### Comments conventions
|
||||
|
||||
@@ -271,21 +275,23 @@ Some of our idiosyncrasies are documented below.
|
||||
|
||||
#### Integration tests conventions
|
||||
|
||||
We prefer our integration tests to be straightforward,
|
||||
branchless (with a few, if any, `if` and `switch` statements),
|
||||
and backend-independent.
|
||||
Ideally, the same test should work for both FerretDB with all backends and MongoDB.
|
||||
We prefer our integration tests to be straightforward and
|
||||
branchless (with a few, if any, `if` and `switch` statements).
|
||||
Ideally, the same test should work for both FerretDB and MongoDB.
|
||||
If that's impossible without some branching, use helpers exported from the `setup` package,
|
||||
such us `FailsForFerretDB`, `SkipForMongoDB`, etc.
|
||||
Please use expected failure rather than skipping the test, whenever it's possible.
|
||||
It makes tracking development progress much easier.
|
||||
|
||||
The bar for using other ways of branching, such as checking error codes and messages, is very high.
|
||||
Writing separate tests might be much better than making a single test that checks error text.
|
||||
|
||||
Also, we should use driver methods as much as possible instead of testing commands directly via `RunCommand`.
|
||||
|
||||
We have a lot of existing helpers to convert the driver's `bson.D` values to our `*types.Document` values,
|
||||
to compare them, etc.
|
||||
In most cases, they should be used instead of (deprecated) `bson.D.Map()`,
|
||||
`bson.Unmarshal`, decoding into a struct with `bson` tags, comparing fields one-by-one, etc.
|
||||
In most cases, helpers such as `AssertEqualDocuments` are used to compare `bson.D` documents.
|
||||
The differences in field order, types, missing fields and others should be reported.
|
||||
If differences are expected such as `version` field, only the field key can be used for `bson.D` document comparison.
|
||||
Use of (deprecated) `bson.D.Map()` or converting `bson.D` to other types using helpers should be avoided when possible.
|
||||
The bar for adding new helpers is very high.
|
||||
Please check all existing ones.
|
||||
|
||||
@@ -297,30 +303,42 @@ For example:
|
||||
const doubleMaxPrec = float64(1<<53 - 1) // 9007199254740991.0: largest double values that could be represented as integer exactly
|
||||
```
|
||||
|
||||
Avoid writing tests that cannot run in parallel with other tests.
|
||||
The `integration` directory contains multiple test packages,
|
||||
and tests from other packages may run at the same time.
|
||||
Such test becomes flaky and hard to maintain.
|
||||
|
||||
#### Integration tests naming guidelines
|
||||
|
||||
<!-- https://github.com/FerretDB/FerretDB/issues/694 -->
|
||||
|
||||
1. Test names should include the name of the command being tested.
|
||||
For instance, `TestDistinct` for testing the distinct command.
|
||||
Use `Test<Command><Operator><Scenario>` format for test names, omitting `<Operator>` and `<Scenario>` if unnecessary.
|
||||
|
||||
1. Include the name of the command (and the operator if any).
|
||||
For example, `TestDistinct` for testing the distinct command.
|
||||
2. Compatibility tests should have `Compat` in the name, following the command.
|
||||
For example, `TestDistinctCompat`.
|
||||
3. If the test doesn't use driver method but runs a command directly via `RunCommand`,
|
||||
the suffix `Command` should be added.
|
||||
3. If the test runs a command directly via `RunCommand`, add the suffix `Command`.
|
||||
For example, `TestDistinctCommand`.
|
||||
4. If the test is both compat and runs a command, the suffix `CommandCompat` should be added.
|
||||
4. If the test is both compat and runs a command, add the suffix `CommandCompat`.
|
||||
For example, `TestInsertCommandCompat`.
|
||||
5. If the file consists of compatibility tests, add the `_compat` suffix.
|
||||
For example, `distinct_compat_test.go`.
|
||||
6. Test names should be descriptive and provide information about the functionality or condition being tested.
|
||||
If the test is checking for a specific error scenario, include the error scenario in the name.
|
||||
6. Use descriptive test names to include the condition or the specific error being tested.
|
||||
For example, `TestAggregateCompatSortDotNotation` for compatibility tests
|
||||
on `aggregate` command `$sort` operator on a document field.
|
||||
7. Keep test names concise, avoiding overly cryptic names.
|
||||
Use abbreviations when appropriate.
|
||||
8. Avoid including test data in the name to maintain clarity and prevent excessively long names.
|
||||
9. Test case names should follow `TitleCase` capitalization style.
|
||||
9. Use `TitleCase` capitalization style for test case names.
|
||||
No spaces, dashes or underscores should be used neither for test names nor for test case names.
|
||||
10. Keep the concatenation of test name segments (test, subtests) within 64 characters
|
||||
10. Use [BSON notation](https://bsonspec.org/spec.html) vocabulary.
|
||||
For example, use `Double` and not `float64`.
|
||||
11. Keep the concatenation of test name segments (test, subtests) within 64 characters
|
||||
to satisfy the maximum limit for database names.
|
||||
12. Use a package for tests in the same scope.
|
||||
Separating into packages can reduce binary sizes and may enhance performance.
|
||||
For example, use `users` package for user management test files.
|
||||
|
||||
### Submitting code changes
|
||||
|
||||
@@ -389,4 +407,4 @@ Before submitting a pull request, please make sure that:
|
||||
created from the [current state](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork)
|
||||
of our main branch.
|
||||
2. Documentation is formatted, linted, and built with `task docs`.
|
||||
3. Documentation is written according to our [writing guide](https://docs.ferretdb.io/v1.24/contributing/writing-guide/).
|
||||
3. Documentation is written according to our [writing guide](https://docs.ferretdb.io/contributing/writing-guide/).
|
||||
@@ -1,28 +1,42 @@
|
||||
# FerretDB
|
||||
|
||||
[](https://pkg.go.dev/github.com/FerretDB/FerretDB/ferretdb)
|
||||
<!-- textlint-disable one-sentence-per-line -->
|
||||
|
||||
> [!TIP]
|
||||
> Looking for FerretDB v1?
|
||||
> It is [there](https://github.com/FerretDB/FerretDB/tree/main-v1).
|
||||
|
||||
<!-- textlint-enable one-sentence-per-line -->
|
||||
|
||||
[](https://github.com/FerretDB/FerretDB/actions/workflows/go.yml)
|
||||
[](https://codecov.io/gh/FerretDB/FerretDB)
|
||||
[](https://codecov.io/gh/FerretDB/FerretDB)
|
||||
|
||||
[](https://github.com/FerretDB/FerretDB/actions/workflows/security.yml)
|
||||
[](https://github.com/FerretDB/FerretDB/actions/workflows/packages.yml)
|
||||
[](https://github.com/FerretDB/FerretDB/actions/workflows/docs.yml)
|
||||
|
||||
FerretDB was founded to become the de-facto open-source substitute to MongoDB.
|
||||
FerretDB is an open-source proxy, converting the MongoDB 5.0+ wire protocol queries to SQL -
|
||||
using PostgreSQL or SQLite as a database engine.
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
<img src="website/static/ferrets-are-working.svg" width="421" height="298" alt="Ferrets are working" title="Ferrets are working">
|
||||
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
_Please pardon the dust as we move FerretDB v2 to this repository._
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Any application<br>Any MongoDB driver"]
|
||||
F{{FerretDB}}
|
||||
P[(PostgreSQL)]
|
||||
S[("SQLite")]
|
||||
P[(PostgreSQL<br>DocumentDB extension)]
|
||||
|
||||
A -- "MongoDB protocol<br>BSON" --> F
|
||||
F -- "PostgreSQL protocol<br>SQL" --> P
|
||||
F -. "SQLite library<br>SQL" .-> S
|
||||
```
|
||||
|
||||
## Why do we need FerretDB?
|
||||
@@ -31,7 +45,7 @@ MongoDB was originally an eye-opening technology for many of us developers,
|
||||
empowering us to build applications faster than using relational databases.
|
||||
In its early days, its ease-to-use and well-documented drivers made MongoDB one of the simplest database solutions available.
|
||||
However, as time passed, MongoDB abandoned its open-source roots;
|
||||
changing the license to [SSPL](https://www.mongodb.com/licensing/server-side-public-license) - making it unusable for many open source and early-stage commercial projects.
|
||||
changing the license to [SSPL](https://www.ferretdb.com/sspl) - making it unusable for many open-source and early-stage commercial projects.
|
||||
|
||||
Most MongoDB users do not require any advanced features offered by MongoDB;
|
||||
however, they need an easy-to-use open-source document database solution.
|
||||
@@ -45,62 +59,47 @@ Features are constantly being added to further increase compatibility and perfor
|
||||
|
||||
We welcome all contributors.
|
||||
See our [public roadmap](https://github.com/orgs/FerretDB/projects/2/views/1),
|
||||
a list of [known differences with MongoDB](https://docs.ferretdb.io/v1.24/diff/),
|
||||
a list of [known differences with MongoDB](https://docs.ferretdb.io/diff/),
|
||||
and [contributing guidelines](CONTRIBUTING.md).
|
||||
|
||||
## Quickstart
|
||||
|
||||
Run this command to start FerretDB with PostgreSQL backend:
|
||||
Run this command to start FerretDB with PostgreSQL:
|
||||
|
||||
```sh
|
||||
docker run -d --rm --name ferretdb -p 27017:27017 ghcr.io/ferretdb/all-in-one
|
||||
docker run -d --rm --name ferretdb -p 27017:27017 ghcr.io/ferretdb/ferretdb-eval:2
|
||||
```
|
||||
|
||||
Alternatively, run this command to start FerretDB with SQLite backend:
|
||||
|
||||
```sh
|
||||
docker run -d --rm --name ferretdb -p 27017:27017 -e FERRETDB_HANDLER=sqlite ghcr.io/ferretdb/all-in-one
|
||||
```
|
||||
|
||||
This command will start a container with FerretDB, PostgreSQL/SQLite, and MongoDB Shell for quick testing and experiments.
|
||||
This command will start a container with FerretDB, pre-packaged PostgreSQL with DocumentDB extension, and MongoDB Shell for quick testing and experiments.
|
||||
However, it is unsuitable for production use cases because it keeps all data inside and loses it on shutdown.
|
||||
See our [Docker quickstart guide](https://docs.ferretdb.io/v1.24/quickstart-guide/docker/) for instructions
|
||||
See our [installation guides](https://docs.ferretdb.io/installation/) for instructions
|
||||
that don't have those problems.
|
||||
|
||||
With that container running, you can:
|
||||
|
||||
- Connect to it with any MongoDB client application using MongoDB URI `mongodb://127.0.0.1:27017/`.
|
||||
- Connect to it with any MongoDB client application using MongoDB URI `mongodb://username:password@127.0.0.1:27017/`.
|
||||
- Connect to it using MongoDB Shell by just running `mongosh`.
|
||||
If you don't have it installed locally, you can run `docker exec -it ferretdb mongosh`.
|
||||
- For the PostgreSQL backend, connect to it by running `docker exec -it ferretdb psql -U username ferretdb`.
|
||||
FerretDB uses PostgreSQL schemas for MongoDB databases.
|
||||
So, if you created some collections in the `test` database using any MongoDB client,
|
||||
you can switch to it by running `SET search_path = 'test';` query
|
||||
and see a list of PostgreSQL tables by running `\d` `psql` command.
|
||||
- For the SQLite backend, connect to it by running `docker exec -it ferretdb sqlite3 /state/<database>.sqlite`.
|
||||
So, if you created some collections in the `test` database using any MongoDB client,
|
||||
run `docker exec -it ferretdb sqlite3 /state/test.sqlite`
|
||||
and see a list of SQLite tables by running `.tables` command.
|
||||
- For PostgreSQL, connect to it by running `docker exec -it ferretdb psql -U username postgres`.
|
||||
|
||||
You can stop the container with `docker stop ferretdb`.
|
||||
|
||||
We also provide binaries and packages for various Linux distributions,
|
||||
as well as [Go library package](https://pkg.go.dev/github.com/FerretDB/FerretDB/ferretdb) that embeds FerretDB into your application.
|
||||
See [our documentation](https://docs.ferretdb.io/v1.24/quickstart-guide/) for more details.
|
||||
We also provide binaries and packages for various Linux distributions.
|
||||
See [our documentation](https://docs.ferretdb.io/installation/) for more details.
|
||||
|
||||
## Building and packaging
|
||||
|
||||
<!-- textlint-disable one-sentence-per-line -->
|
||||
|
||||
> [!NOTE]
|
||||
> We strongly advise users not to build FerretDB themselves.
|
||||
> We advise users not to build FerretDB themselves.
|
||||
> Instead, use binaries, Docker images, or packages provided by us.
|
||||
|
||||
<!-- textlint-enable one-sentence-per-line -->
|
||||
|
||||
FerretDB could be built as any other Go program,
|
||||
but a few generated files and build tags could affect it.
|
||||
See [there](https://pkg.go.dev/github.com/FerretDB/FerretDB/build/version) for more details.
|
||||
See [there](https://pkg.go.dev/github.com/FerretDB/FerretDB/v2/build/version) for more details.
|
||||
|
||||
## Managed FerretDB at cloud providers
|
||||
|
||||
@@ -109,15 +108,12 @@ See [there](https://pkg.go.dev/github.com/FerretDB/FerretDB/build/version) for m
|
||||
- [Elestio](https://elest.io/open-source/ferretdb)
|
||||
- [Cozystack](https://cozystack.io/docs/components/#managed-ferretdb).
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Documentation for users](https://docs.ferretdb.io/).
|
||||
- [Documentation for Go developers about embeddable FerretDB](https://pkg.go.dev/github.com/FerretDB/FerretDB/ferretdb).
|
||||
|
||||
## Community
|
||||
|
||||
- Website and blog: https://www.ferretdb.com/.
|
||||
- Twitter: [@ferret_db](https://twitter.com/ferret_db).
|
||||
- Website: https://www.ferretdb.com/.
|
||||
- Blog: https://blog.ferretdb.io/.
|
||||
- Documentation: https://docs.ferretdb.io/.
|
||||
- X (Twitter): [@ferret_db](https://x.com/ferret_db).
|
||||
- Mastodon: [@ferretdb@techhub.social](https://techhub.social/@ferretdb).
|
||||
- [Slack chat](https://join.slack.com/t/ferretdb/shared_invite/zt-zqe9hj8g-ZcMG3~5Cs5u9uuOPnZB8~A) for quick questions.
|
||||
- [GitHub Discussions](https://github.com/FerretDB/FerretDB/discussions) for longer topics.
|
||||
|
||||
+123
-247
@@ -8,14 +8,15 @@ env:
|
||||
vars:
|
||||
SHARD_INDEX: 0
|
||||
SHARD_TOTAL: 0
|
||||
DISABLE_PUSHDOWN: false
|
||||
TEST_RUN: ""
|
||||
TEST_TIMEOUT: 35m
|
||||
NO_XFAIL: false
|
||||
BENCH_TIME: 5s
|
||||
TESTJS_PORT: 27017
|
||||
RACE_FLAG: -race={{and (ne OS "windows") (ne ARCH "arm") (ne ARCH "riscv64")}}
|
||||
BUILD_TAGS: ferretdb_debug,ferretdb_hana
|
||||
SERVICES: postgres postgres_secured mysql mongodb mongodb_secured jaeger
|
||||
BUILD_TAGS: ferretdb_dev
|
||||
MODE: diff-normal
|
||||
SERVICES: postgres mongodb mongodb-secure jaeger
|
||||
|
||||
tasks:
|
||||
# invoked when `task` is run without arguments
|
||||
@@ -30,7 +31,6 @@ tasks:
|
||||
- task: test-unit
|
||||
- task: lint
|
||||
- task: security
|
||||
- task: fmt-yaml
|
||||
- task: docs
|
||||
|
||||
init-tools:
|
||||
@@ -105,6 +105,11 @@ tasks:
|
||||
- docker compose ps --all
|
||||
- docker compose logs --follow
|
||||
|
||||
env-logs-collect:
|
||||
cmds:
|
||||
- docker compose ps --all
|
||||
- docker compose logs
|
||||
|
||||
env-up:
|
||||
desc: "Start development environment"
|
||||
deps: [env-up-detach, env-setup]
|
||||
@@ -153,17 +158,18 @@ tasks:
|
||||
desc: "Generate (and format) Go code"
|
||||
cmds:
|
||||
- go generate -x ./...
|
||||
#- bin/deadcode{{exeExt}} -generated -test -filter=documentdb_api ./cmd/ferretdb
|
||||
- bin/task{{exeExt}} -d integration integration-gen
|
||||
- task: fmt-go
|
||||
|
||||
# invoked by FerretDB/github-actions/linters action
|
||||
fmt:
|
||||
deps: [fmt-go]
|
||||
|
||||
# invoked by FerretDB/github-actions/linters action
|
||||
fmt-go:
|
||||
desc: "Format Go code"
|
||||
cmds:
|
||||
- bin/goimports{{exeExt}} -local=github.com/FerretDB/FerretDB -w .
|
||||
- bin/goimports{{exeExt}} -local=github.com/FerretDB/FerretDB/v2 -w .
|
||||
- bin/gofumpt{{exeExt}} -w .
|
||||
|
||||
test:
|
||||
@@ -173,26 +179,22 @@ tasks:
|
||||
test-unit-short:
|
||||
desc: "Run short unit tests (with caching)"
|
||||
cmds:
|
||||
- go test -short -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverpkg=./... -coverprofile=cover.txt ./...
|
||||
- go test -short -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverprofile=cover.txt ./...
|
||||
- bin/task{{exeExt}} -d tools tools-test-short
|
||||
|
||||
test-unit:
|
||||
desc: "Run all unit tests"
|
||||
cmds:
|
||||
- go test -count=1 -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverpkg=./... -coverprofile=cover.txt ./...
|
||||
- go test -count=1 -timeout={{.TEST_TIMEOUT}} {{.RACE_FLAG}} -tags={{.BUILD_TAGS}} -shuffle=on -coverprofile=cover.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
|
||||
|
||||
test-integration:
|
||||
desc: "Run integration tests for several backends in parallel"
|
||||
deps:
|
||||
- test-integration-postgresql
|
||||
- test-integration-sqlite
|
||||
# no test-integration-hana
|
||||
- test-integration-mongodb
|
||||
desc: "Run integration tests in parallel"
|
||||
deps: [test-integration-postgresql, test-integration-mongodb]
|
||||
|
||||
test-integration-postgresql:
|
||||
desc: "Run integration tests for `postgresql` backend"
|
||||
desc: "Run integration tests for PostgreSQL/DocumentDB"
|
||||
deps: [gen-version]
|
||||
dir: integration
|
||||
cmds:
|
||||
@@ -202,6 +204,7 @@ tasks:
|
||||
--shard-total={{.SHARD_TOTAL}}
|
||||
--run='{{.TEST_RUN}}'
|
||||
--skip='{{.TEST_SKIP}}'
|
||||
--raw-prefix=postgresql-{{.SHARD_INDEX}}-{{.SHARD_TOTAL}}
|
||||
--
|
||||
-count=1
|
||||
-timeout={{.TEST_TIMEOUT}}
|
||||
@@ -211,89 +214,10 @@ tasks:
|
||||
-coverpkg=../...
|
||||
-coverprofile=integration-postgresql.txt
|
||||
./...
|
||||
-target-backend=ferretdb-postgresql
|
||||
-target-tls
|
||||
-postgresql-url='postgres://username@127.0.0.1:5432/ferretdb?search_path='
|
||||
-compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=client.pem&tlsCaFile=rootCA-cert.pem&replicaSet=rs0'
|
||||
-disable-pushdown={{.DISABLE_PUSHDOWN}}
|
||||
|
||||
test-integration-sqlite:
|
||||
desc: "Run integration tests for `sqlite` backend"
|
||||
deps: [gen-version]
|
||||
dir: integration
|
||||
cmds:
|
||||
- >
|
||||
../bin/envtool{{exeExt}} tests run
|
||||
--shard-index={{.SHARD_INDEX}}
|
||||
--shard-total={{.SHARD_TOTAL}}
|
||||
--run='{{.TEST_RUN}}'
|
||||
--skip='{{.TEST_SKIP}}'
|
||||
--
|
||||
-count=1
|
||||
-timeout={{.TEST_TIMEOUT}}
|
||||
{{.RACE_FLAG}}
|
||||
-tags={{.BUILD_TAGS}}
|
||||
-shuffle=on
|
||||
-coverpkg=../...
|
||||
-coverprofile=integration-sqlite.txt
|
||||
./...
|
||||
-target-backend=ferretdb-sqlite
|
||||
-sqlite-url=file:../tmp/sqlite-tests/
|
||||
-target-tls
|
||||
-compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=client.pem&tlsCaFile=rootCA-cert.pem&replicaSet=rs0'
|
||||
-disable-pushdown={{.DISABLE_PUSHDOWN}}
|
||||
|
||||
test-integration-mysql:
|
||||
desc: "Run integration tests for `mysql` handler"
|
||||
deps: [gen-version]
|
||||
dir: integration
|
||||
cmds:
|
||||
- >
|
||||
../bin/envtool{{exeExt}} tests run
|
||||
--shard-index={{.SHARD_INDEX}}
|
||||
--shard-total={{.SHARD_TOTAL}}
|
||||
--run='{{.TEST_RUN}}'
|
||||
--skip='{{.TEST_SKIP}}'
|
||||
--
|
||||
-count=1
|
||||
-timeout={{.TEST_TIMEOUT}}
|
||||
{{.RACE_FLAG}}
|
||||
-tags={{.BUILD_TAGS}}
|
||||
-shuffle=on
|
||||
-coverpkg=../...
|
||||
-coverprofile=integration-mysql.txt
|
||||
./...
|
||||
-target-backend=ferretdb-mysql
|
||||
-target-tls
|
||||
-mysql-url='mysql://username:password@127.0.0.1:3306/ferretdb'
|
||||
-compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=client.pem&tlsCaFile=rootCA-cert.pem&replicaSet=rs0'
|
||||
-disable-pushdown={{.DISABLE_PUSHDOWN}}
|
||||
|
||||
test-integration-hana:
|
||||
desc: "Run integration tests for `hana` handler"
|
||||
deps: [gen-version]
|
||||
dir: integration
|
||||
cmds:
|
||||
- >
|
||||
../bin/envtool{{exeExt}} tests run
|
||||
--shard-index={{.SHARD_INDEX}}
|
||||
--shard-total={{.SHARD_TOTAL}}
|
||||
--run='{{.TEST_RUN}}'
|
||||
--skip='{{.TEST_SKIP}}'
|
||||
--
|
||||
-count=1
|
||||
-timeout={{.TEST_TIMEOUT}}
|
||||
{{.RACE_FLAG}}
|
||||
-tags={{.BUILD_TAGS}}
|
||||
-shuffle=on
|
||||
-coverpkg=../...
|
||||
-coverprofile=integration-hana.txt
|
||||
./...
|
||||
-target-backend=ferretdb-hana
|
||||
-target-tls
|
||||
-hana-url=$FERRETDB_HANA_URL
|
||||
-compat-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=client.pem&tlsCaFile=rootCA-cert.pem&replicaSet=rs0'
|
||||
-disable-pushdown={{.DISABLE_PUSHDOWN}}
|
||||
-target-backend=ferretdb
|
||||
-postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
|
||||
-compat-url='mongodb://username:password@127.0.0.1:47017/?replicaSet=rs0'
|
||||
-no-xfail={{.NO_XFAIL}}
|
||||
|
||||
test-integration-mongodb:
|
||||
desc: "Run integration tests for MongoDB"
|
||||
@@ -306,6 +230,7 @@ tasks:
|
||||
--shard-total={{.SHARD_TOTAL}}
|
||||
--run='{{.TEST_RUN}}'
|
||||
--skip='{{.TEST_SKIP}}'
|
||||
--raw-prefix=mongodb-{{.SHARD_INDEX}}-{{.SHARD_TOTAL}}
|
||||
--
|
||||
-count=1
|
||||
-timeout={{.TEST_TIMEOUT}}
|
||||
@@ -315,8 +240,9 @@ tasks:
|
||||
-coverpkg=../...
|
||||
-coverprofile=integration-mongodb.txt
|
||||
./...
|
||||
-target-url='mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=client.pem&tlsCaFile=rootCA-cert.pem&replicaSet=rs0'
|
||||
-target-url='mongodb://username:password@127.0.0.1:47017/?replicaSet=rs0'
|
||||
-target-backend=mongodb
|
||||
-no-xfail={{.NO_XFAIL}}
|
||||
|
||||
bench-unit:
|
||||
desc: "Run unit benchmarks"
|
||||
@@ -326,124 +252,36 @@ tasks:
|
||||
- bin/benchstat{{exeExt}} old.txt new.txt
|
||||
|
||||
run:
|
||||
desc: "Run FerretDB with `postgresql` backend"
|
||||
desc: "Run FerretDB without auth"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--mode={{.MODE}}
|
||||
--no-auth
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-normal
|
||||
--repl-set-name=rs0
|
||||
--handler=pg
|
||||
--postgresql-url='postgres://username@127.0.0.1:5432/ferretdb?search_path='
|
||||
--test-records-dir=tmp/records
|
||||
--listen-data-api-addr=:8899
|
||||
--proxy-addr=127.0.0.1:37017
|
||||
--postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
|
||||
--dev-records-dir=tmp/records
|
||||
--otel-traces-url=http://127.0.0.1:4318/v1/traces
|
||||
|
||||
run-sqlite:
|
||||
desc: "Run FerretDB with `sqlite` backend"
|
||||
run-secure:
|
||||
desc: "Run FerretDB with auth"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- bin/envtool{{exeExt}} shell mkdir tmp/sqlite
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--mode={{.MODE}}
|
||||
--listen-addr=:27017
|
||||
--listen-data-api-addr=:8899
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-normal
|
||||
--handler=sqlite
|
||||
--sqlite-url=file:tmp/sqlite/
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
run-mysql:
|
||||
desc: "Run FerretDB with `mysql` backend"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-normal
|
||||
--handler=mysql
|
||||
--mysql-url='mysql://username@127.0.0.1:3306/ferretdb'
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
# set FERRETDB_HANA_URL environment variable to use it
|
||||
run-hana:
|
||||
desc: "Run FerretDB with `hana` backend"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-normal
|
||||
--handler=hana
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
run-secured:
|
||||
desc: "Run FerretDB with `postgresql` backend (TLS, auth required)"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=''
|
||||
--listen-tls=:27018
|
||||
--listen-tls-cert-file=./build/certs/server-cert.pem
|
||||
--listen-tls-key-file=./build/certs/server-key.pem
|
||||
--listen-tls-ca-file=./build/certs/rootCA-cert.pem
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-normal
|
||||
--handler=pg
|
||||
--postgresql-url='postgres://127.0.0.1:5433/ferretdb?search_path='
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
run-proxy:
|
||||
desc: "Run FerretDB in the diff-proxy mode"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-proxy
|
||||
--handler=pg
|
||||
--postgresql-url='postgres://username@127.0.0.1:5432/ferretdb?search_path='
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
run-sqlite-proxy:
|
||||
desc: "Run FerretDB with `sqlite` handler in the diff-proxy mode"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- bin/envtool{{exeExt}} shell mkdir tmp/sqlite
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=:27017
|
||||
--proxy-addr=127.0.0.1:47017
|
||||
--mode=diff-proxy
|
||||
--handler=sqlite
|
||||
--sqlite-url=file:tmp/sqlite/
|
||||
--test-records-dir=tmp/records
|
||||
|
||||
run-proxy-secured:
|
||||
desc: "Run FerretDB in the diff-proxy mode (TLS, auth required)"
|
||||
deps: [build-host]
|
||||
cmds:
|
||||
- >
|
||||
bin/ferretdb{{exeExt}}
|
||||
--listen-addr=''
|
||||
--listen-tls=:27018
|
||||
--listen-tls-cert-file=./build/certs/server-cert.pem
|
||||
--listen-tls-key-file=./build/certs/server-key.pem
|
||||
--listen-tls-ca-file=./build/certs/rootCA-cert.pem
|
||||
--proxy-addr=127.0.0.1:47018
|
||||
--proxy-tls-cert-file=./build/certs/client-cert.pem
|
||||
--proxy-tls-key-file=./build/certs/client-key.pem
|
||||
--proxy-tls-ca-file=./build/certs/rootCA-cert.pem
|
||||
--mode=diff-proxy
|
||||
--handler=pg
|
||||
--postgresql-url='postgres://username@127.0.0.1:5433/ferretdb?search_path='
|
||||
--test-records-dir=tmp/records
|
||||
--repl-set-name=rs0
|
||||
--postgresql-url='postgres://username:password@127.0.0.1:5432/postgres'
|
||||
--dev-records-dir=tmp/records
|
||||
--otel-traces-url=http://127.0.0.1:4318/v1/traces
|
||||
|
||||
# invoked by FerretDB/github-actions/linters action
|
||||
lint:
|
||||
desc: "Run linters"
|
||||
cmds:
|
||||
@@ -451,8 +289,7 @@ tasks:
|
||||
- bin/envtool{{exeExt}} shell mkdir tmp/githubcache
|
||||
- bin/golangci-lint{{exeExt}} run --config=.golangci.yml
|
||||
- bin/golangci-lint{{exeExt}} run --config=.golangci-new.yml
|
||||
- bin/go-consistent{{exeExt}} -pedantic ./cmd/... ./internal/... ./build/... ./ferretdb/...
|
||||
|
||||
- bin/go-consistent{{exeExt}} -pedantic ./cmd/... ./internal/... ./build/...
|
||||
- go vet -vettool=bin/checkswitch{{exeExt}} ./...
|
||||
- go vet -vettool=bin/checkcomments{{exeExt}} ./...
|
||||
|
||||
@@ -483,17 +320,7 @@ tasks:
|
||||
psql:
|
||||
desc: "Run psql"
|
||||
cmds:
|
||||
- docker compose exec -e PGPASSWORD=password postgres psql -U username -d ferretdb
|
||||
|
||||
sqlite3:
|
||||
desc: "Run sqlite3"
|
||||
cmds:
|
||||
- sqlite3 tmp/sqlite/*.sqlite
|
||||
|
||||
mysql:
|
||||
desc: "Run mysql"
|
||||
cmds:
|
||||
- docker compose exec -e MYSQL_PASSWORD=password mysql mysql -u username -ppassword ferretdb
|
||||
- docker compose exec postgres psql -p 5432 -U username -d postgres
|
||||
|
||||
mongosh:
|
||||
desc: "Run MongoDB shell (`mongosh`)"
|
||||
@@ -503,17 +330,32 @@ tasks:
|
||||
--verbose --eval 'disableTelemetry()' --shell
|
||||
'mongodb://host.docker.internal:27017/?heartbeatFrequencyMS=300000'
|
||||
|
||||
mongosh-secured:
|
||||
desc: "Run MongoDB shell (`mongosh`) with TLS and auth"
|
||||
mongosh-secure:
|
||||
desc: "Run MongoDB shell (`mongosh`) with auth"
|
||||
cmds:
|
||||
- >
|
||||
docker compose exec mongodb mongosh
|
||||
--verbose --eval 'disableTelemetry()' --shell
|
||||
'mongodb://username:password@host.docker.internal:27018/?authMechanism=PLAIN&tls=true&tlsCertificateKeyFile=/etc/certs/client.pem&tlsCaFile=/etc/certs/rootCA-cert.pem'
|
||||
'mongodb://username:password@host.docker.internal:27017/?heartbeatFrequencyMS=300000'
|
||||
|
||||
mongo:
|
||||
desc: "Run legacy MongoDB shell (`mongo`)"
|
||||
cmds:
|
||||
- >
|
||||
docker compose run --rm legacy-mongo-shell
|
||||
'mongodb://username:password@host.docker.internal:27017/'
|
||||
|
||||
testsql:
|
||||
desc: "Run pgTAP with test.sql script"
|
||||
cmds:
|
||||
- docker compose exec postgres pg_prove --ext .sql --verbose -p 5432 -U username -d postgres /testscripts/
|
||||
|
||||
testjs:
|
||||
desc: "Run legacy MongoDB shell (`mongo`) with test.js script"
|
||||
cmds:
|
||||
# Authentication with username:password will not work
|
||||
# because this user is created by setup with a different salt length,
|
||||
# and there are no other credentials to use.
|
||||
- >
|
||||
docker compose run --rm legacy-mongo-shell
|
||||
'mongodb://host.docker.internal:{{.TESTJS_PORT}}/'
|
||||
@@ -529,7 +371,6 @@ tasks:
|
||||
--bootstrap=true
|
||||
--use=false
|
||||
--config=./build/buildkitd.toml
|
||||
--driver=docker-container
|
||||
--driver-opt network=host
|
||||
--driver-opt env.JAEGER_TRACE=127.0.0.1:6831
|
||||
--driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=-1
|
||||
@@ -578,7 +419,7 @@ tasks:
|
||||
vars: [DOCKER_ARCH, DIR_ARCH, BIN_FILE, BIN_DIR]
|
||||
|
||||
build-development:
|
||||
desc: "Build development debug binaries"
|
||||
desc: "Build development binaries"
|
||||
cmds:
|
||||
- echo 'bin-development' > build/version/package.txt
|
||||
- bin/envtool{{exeExt}} shell rmdir tmp/build
|
||||
@@ -587,9 +428,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/development
|
||||
TARGET: development-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: build-move-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -598,7 +439,7 @@ tasks:
|
||||
BIN_DIR: bin-dev
|
||||
|
||||
build-production:
|
||||
desc: "Build production non-debug binaries"
|
||||
desc: "Build production binaries"
|
||||
cmds:
|
||||
- echo 'bin' > build/version/package.txt
|
||||
- bin/envtool{{exeExt}} shell rmdir tmp/build
|
||||
@@ -607,9 +448,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/production
|
||||
TARGET: production-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: build-move-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -631,14 +472,14 @@ tasks:
|
||||
vars: [FILE]
|
||||
|
||||
# TODO https://github.com/FerretDB/FerretDB/issues/2212
|
||||
docker-all-in-one-push:
|
||||
docker-evaluation-push:
|
||||
cmds:
|
||||
- echo 'docker-all-in-one' > build/version/package.txt
|
||||
- echo 'docker-evaluation' > build/version/package.txt
|
||||
- task: docker-build
|
||||
vars:
|
||||
FILE: ferretdb/all-in-one
|
||||
TARGET: all-in-one
|
||||
PLATFORM: linux/amd64,linux/arm64 # no mongosh for arm/v6 and arm/v7
|
||||
FILE: ferretdb/evaluation
|
||||
TARGET: evaluation
|
||||
PLATFORM: linux/amd64 # no DocumentDB for linux/arm64 yet
|
||||
OUTPUT: type=image,push=true
|
||||
requires:
|
||||
vars: [DOCKER_IMAGES]
|
||||
@@ -651,7 +492,7 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/development
|
||||
TARGET: development
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=image,push=true
|
||||
requires:
|
||||
vars: [DOCKER_IMAGES]
|
||||
@@ -664,7 +505,42 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/production
|
||||
TARGET: production
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=image,push=true
|
||||
requires:
|
||||
vars: [DOCKER_IMAGES]
|
||||
|
||||
docker-documentdb:
|
||||
desc: "Build `documentdb-local` Docker image"
|
||||
cmds:
|
||||
- task: docker-build
|
||||
vars:
|
||||
FILE: postgres-documentdb/{{.FILE}}
|
||||
TARGET: "{{.FILE}}"
|
||||
PLATFORM: linux/amd64
|
||||
DOCKER_IMAGES: documentdb-local
|
||||
OUTPUT: type=docker
|
||||
requires:
|
||||
vars: [FILE]
|
||||
|
||||
docker-documentdb-development-push:
|
||||
cmds:
|
||||
- task: docker-build
|
||||
vars:
|
||||
FILE: postgres-documentdb/development
|
||||
TARGET: development
|
||||
PLATFORM: linux/amd64
|
||||
OUTPUT: type=image,push=true
|
||||
requires:
|
||||
vars: [DOCKER_IMAGES]
|
||||
|
||||
docker-documentdb-production-push:
|
||||
cmds:
|
||||
- task: docker-build
|
||||
vars:
|
||||
FILE: postgres-documentdb/production
|
||||
TARGET: production
|
||||
PLATFORM: linux/amd64
|
||||
OUTPUT: type=image,push=true
|
||||
requires:
|
||||
vars: [DOCKER_IMAGES]
|
||||
@@ -713,9 +589,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/development
|
||||
TARGET: development-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-build
|
||||
vars:
|
||||
PACKAGER: deb
|
||||
@@ -723,7 +599,7 @@ tasks:
|
||||
PACKAGE_ARCH: '{{replace "/" "" .ITEM | replace "v" ""}}' # arm/v7 -> arm7
|
||||
DIR_ARCH: '{{replace "/" "_" .ITEM}}' # arm/v7 -> arm_v7
|
||||
FILE_SUFFIX: 'dev-linux-{{replace "/" "" .ITEM}}' # arm/v7 -> armv7
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-deb-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -741,9 +617,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/production
|
||||
TARGET: production-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-build
|
||||
vars:
|
||||
PACKAGER: deb
|
||||
@@ -751,7 +627,7 @@ tasks:
|
||||
PACKAGE_ARCH: '{{replace "/" "" .ITEM | replace "v" ""}}' # arm/v7 -> arm7
|
||||
DIR_ARCH: '{{replace "/" "_" .ITEM}}' # arm/v7 -> arm_v7
|
||||
FILE_SUFFIX: 'linux-{{replace "/" "" .ITEM}}' # arm/v7 -> armv7
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-deb-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -781,9 +657,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/development
|
||||
TARGET: development-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-build
|
||||
vars:
|
||||
PACKAGER: rpm
|
||||
@@ -791,7 +667,7 @@ tasks:
|
||||
PACKAGE_ARCH: '{{replace "/" "" .ITEM | replace "v" ""}}' # arm/v7 -> arm7
|
||||
DIR_ARCH: '{{replace "/" "_" .ITEM}}' # arm/v7 -> arm_v7
|
||||
FILE_SUFFIX: 'dev-linux-{{replace "/" "" .ITEM}}' # arm/v7 -> armv7
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-rpm-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -809,9 +685,9 @@ tasks:
|
||||
vars:
|
||||
FILE: ferretdb/production
|
||||
TARGET: production-binary
|
||||
PLATFORM: linux/amd64,linux/arm64,linux/arm/v7 # TODO https://github.com/FerretDB/FerretDB/issues/3354
|
||||
PLATFORM: linux/amd64,linux/arm64
|
||||
OUTPUT: type=local,dest=tmp/build
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-build
|
||||
vars:
|
||||
PACKAGER: rpm
|
||||
@@ -819,7 +695,7 @@ tasks:
|
||||
PACKAGE_ARCH: '{{replace "/" "" .ITEM | replace "v" ""}}' # arm/v7 -> arm7
|
||||
DIR_ARCH: '{{replace "/" "_" .ITEM}}' # arm/v7 -> arm_v7
|
||||
FILE_SUFFIX: 'linux-{{replace "/" "" .ITEM}}' # arm/v7 -> armv7
|
||||
- for: [amd64, arm64, arm/v7]
|
||||
- for: [amd64, arm64]
|
||||
task: packages-rpm-check
|
||||
vars:
|
||||
DOCKER_ARCH: "{{.ITEM}}" # arm/v7
|
||||
@@ -857,7 +733,7 @@ tasks:
|
||||
"**/*.md" "**/*.mdx"
|
||||
- >
|
||||
docker compose run --rm markdownlint --config "build/.markdownlint.yml"
|
||||
"**/*.md" "**/*.mdx"
|
||||
"**/*.md" "**/*.mdx" "#build/postgres-documentdb/documentdb/"
|
||||
- bin/checkdocs
|
||||
|
||||
docs-fmt:
|
||||
@@ -925,7 +801,7 @@ tasks:
|
||||
- docker compose run --rm --service-ports wrangler pages deployment create --project-name=blog website/build
|
||||
|
||||
changelog:
|
||||
desc: "Generate changelogs locally for a given milestone."
|
||||
desc: "Generate changelogs locally for a given milestone"
|
||||
cmds:
|
||||
- bin/generatechangelog -milestone={{.MILESTONE_TITLE}} -previous={{.PREVIOUS_MILESTONE_TITLE}}
|
||||
requires:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
FROM citusdata/citus:12.1
|
||||
@@ -1 +0,0 @@
|
||||
FROM mongo:7.0.8
|
||||
@@ -0,0 +1,15 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM mongo:7.0.8
|
||||
|
||||
# If you encounter an "unknown instruction" error there,
|
||||
# please update Docker to the latest version.
|
||||
|
||||
# see https://github.com/docker-library/mongo/issues/475
|
||||
RUN <<EOF
|
||||
set -ex
|
||||
|
||||
echo 'topsecret' > /etc/mongod_keyfile.txt
|
||||
chmod 0400 /etc/mongod_keyfile.txt
|
||||
chown 999:999 /etc/mongod_keyfile.txt
|
||||
EOF
|
||||
@@ -1 +0,0 @@
|
||||
FROM mysql:8.4.0
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM ghcr.io/ferretdb/postgres-documentdb-dev:16.6-0.100-0
|
||||
# FROM ghcr.io/ferretdb/postgres-documentdb:16.6-0.100-0
|
||||
@@ -1 +0,0 @@
|
||||
FROM postgres:16.4
|
||||
@@ -9,9 +9,9 @@ ExecStart=/usr/bin/ferretdb
|
||||
Restart=on-failure
|
||||
|
||||
# Configure the FerretDB service with `systemctl edit ferretdb`.
|
||||
# For more configuration options check https://docs.ferretdb.io/v1.24/configuration/flags/
|
||||
# For more configuration options check https://docs.ferretdb.io/configuration/flags/
|
||||
|
||||
Environment="FERRETDB_POSTGRESQL_URL=postgres://127.0.0.1:5432/ferretdb"
|
||||
Environment="FERRETDB_POSTGRESQL_URL=postgres://127.0.0.1:5432/postgres"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
/usr/local/bin/ferretdb &
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
exec /usr/bin/runsvdir /etc/service
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
exec /ferretdb
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
exec /usr/local/bin/docker-entrypoint.sh postgres
|
||||
@@ -14,7 +14,7 @@ ARG LABEL_COMMIT
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23.5 AS development-prepare
|
||||
|
||||
# use a single directory for all Go caches to simpliy RUN --mount commands below
|
||||
# use a single directory for all Go caches to simplify RUN --mount commands below
|
||||
ENV GOPATH=/cache/gopath
|
||||
ENV GOCACHE=/cache/gocache
|
||||
ENV GOMODCACHE=/cache/gomodcache
|
||||
@@ -39,7 +39,6 @@ EOF
|
||||
FROM golang:1.23.5 AS development-build
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
ARG LABEL_VERSION
|
||||
ARG LABEL_COMMIT
|
||||
@@ -70,14 +69,10 @@ git status
|
||||
# because v2+ is problematic for some virtualization platforms and older hardware.
|
||||
export GOAMD64=v1
|
||||
|
||||
# Set GOARM explicitly due to https://github.com/docker-library/golang/issues/494.
|
||||
export GOARM=${TARGETVARIANT#v}
|
||||
|
||||
export CGO_ENABLED=1
|
||||
|
||||
# Disable race detector on arm64 due to https://github.com/golang/go/issues/29948
|
||||
# (and that happens on GitHub-hosted Actions runners).
|
||||
# Also disable it on arm/v6 and arm/v7 because it is not supported there.
|
||||
RACE=false
|
||||
if test "$TARGETARCH" = "amd64"
|
||||
then
|
||||
@@ -91,7 +86,7 @@ go env
|
||||
# check if stdlib was cached
|
||||
go install -v -race=$RACE std
|
||||
|
||||
go build -v -o=bin/ferretdb -race=$RACE -tags=ferretdb_debug -coverpkg=./... ./cmd/ferretdb
|
||||
go build -v -o=bin/ferretdb -race=$RACE -tags=ferretdb_dev -coverpkg=./... ./cmd/ferretdb
|
||||
|
||||
go version -m bin/ferretdb
|
||||
bin/ferretdb --version
|
||||
@@ -116,7 +111,7 @@ RUN mkdir /tmp/cover
|
||||
|
||||
COPY --from=development-build /src/bin/ferretdb /ferretdb
|
||||
|
||||
ENTRYPOINT ["/ferretdb"]
|
||||
ENTRYPOINT [ "/ferretdb" ]
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=5s --retries=1 --start-period=30s --start-interval=5s \
|
||||
CMD ["/ferretdb", "ping"]
|
||||
@@ -130,17 +125,15 @@ ENV FERRETDB_LISTEN_ADDR=:27017
|
||||
# ENV FERRETDB_LISTEN_TLS=:27018
|
||||
ENV FERRETDB_DEBUG_ADDR=:8088
|
||||
ENV FERRETDB_STATE_DIR=/state
|
||||
ENV FERRETDB_SQLITE_URL=file:/state/
|
||||
|
||||
ARG LABEL_VERSION
|
||||
ARG LABEL_COMMIT
|
||||
|
||||
# TODO https://github.com/FerretDB/FerretDB/issues/2212
|
||||
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative"
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative (development image)"
|
||||
LABEL org.opencontainers.image.revision="${LABEL_COMMIT}"
|
||||
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
|
||||
LABEL org.opencontainers.image.title="FerretDB"
|
||||
LABEL org.opencontainers.image.title="FerretDB (development image)"
|
||||
LABEL org.opencontainers.image.url="https://www.ferretdb.com/"
|
||||
LABEL org.opencontainers.image.vendor="FerretDB Inc."
|
||||
LABEL org.opencontainers.image.version="${LABEL_VERSION}"
|
||||
@@ -1,8 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Dockerfile for all-in-one releases (`all-in-one` image).
|
||||
# It always uses native compilation with race detector
|
||||
# because packages are only available for amd64 and arm64 anyway.
|
||||
# for evaluation (formerly "all-in-one") releases (`ferretdb-eval` image)
|
||||
|
||||
# While we already know commit and version from commit.txt and version.txt inside image,
|
||||
# it is not possible to use them in LABELs for the final image.
|
||||
@@ -14,9 +12,9 @@ ARG LABEL_COMMIT
|
||||
|
||||
# prepare stage
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23.5 AS all-in-one-prepare
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23.5 AS evaluation-prepare
|
||||
|
||||
# use a single directory for all Go caches to simpliy RUN --mount commands below
|
||||
# use a single directory for all Go caches to simplify RUN --mount commands below
|
||||
ENV GOPATH=/cache/gopath
|
||||
ENV GOCACHE=/cache/gocache
|
||||
ENV GOMODCACHE=/cache/gomodcache
|
||||
@@ -38,7 +36,7 @@ EOF
|
||||
|
||||
# build stage
|
||||
|
||||
FROM golang:1.23.5 AS all-in-one-build
|
||||
FROM golang:1.23.5 AS evaluation-build
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -60,7 +58,7 @@ WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
# to add a dependency
|
||||
COPY --from=all-in-one-prepare /src/go.mod /src/go.sum /src/
|
||||
COPY --from=evaluation-prepare /src/go.mod /src/go.sum /src/
|
||||
|
||||
RUN --mount=type=cache,target=/cache <<EOF
|
||||
set -ex
|
||||
@@ -71,13 +69,10 @@ git status
|
||||
# because v2+ is problematic for some virtualization platforms and older hardware.
|
||||
export GOAMD64=v1
|
||||
|
||||
# GOARM is not set because it is ignored for arm64
|
||||
|
||||
export CGO_ENABLED=1
|
||||
|
||||
# Disable race detector on arm64 due to https://github.com/golang/go/issues/29948
|
||||
# (and that happens on GitHub-hosted Actions runners).
|
||||
# Also disable it on arm/v6 and arm/v7 because it is not supported there.
|
||||
RACE=false
|
||||
if test "$TARGETARCH" = "amd64"
|
||||
then
|
||||
@@ -91,7 +86,7 @@ go env
|
||||
# check if stdlib was cached
|
||||
go install -v -race=$RACE std
|
||||
|
||||
go build -v -o=bin/ferretdb -race=$RACE -tags=ferretdb_debug -coverpkg=./... ./cmd/ferretdb
|
||||
go build -v -o=bin/ferretdb -race=$RACE -tags=ferretdb_dev -coverpkg=./... ./cmd/ferretdb
|
||||
|
||||
go version -m bin/ferretdb
|
||||
bin/ferretdb --version
|
||||
@@ -100,26 +95,11 @@ EOF
|
||||
|
||||
# final stage
|
||||
|
||||
FROM postgres:16.4 AS all-in-one
|
||||
|
||||
COPY --from=all-in-one-build /src/bin/ferretdb /ferretdb
|
||||
|
||||
ENV GOCOVERDIR=/tmp/cover
|
||||
ENV GORACE=halt_on_error=1,history_size=2
|
||||
|
||||
RUN mkdir /tmp/cover
|
||||
|
||||
# all-in-one hacks start there
|
||||
|
||||
COPY --from=all-in-one-build /src/build/ferretdb/all-in-one/ferretdb.sh /etc/service/ferretdb/run
|
||||
COPY --from=all-in-one-build /src/build/ferretdb/all-in-one/postgresql.sh /etc/service/postgresql/run
|
||||
COPY --from=all-in-one-build /src/build/ferretdb/all-in-one/entrypoint.sh /entrypoint.sh
|
||||
FROM ghcr.io/ferretdb/postgres-documentdb:16.6-0.100-0 AS evaluation
|
||||
|
||||
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt <<EOF
|
||||
set -ex
|
||||
|
||||
apt update
|
||||
apt install -y curl runit sqlite3
|
||||
mkdir /tmp/cover /tmp/state
|
||||
chown postgres:postgres /tmp/cover /tmp/state
|
||||
|
||||
curl -L https://pgp.mongodb.com/server-7.0.asc | apt-key add -
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/debian bookworm/mongodb-org/7.0 main" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list
|
||||
@@ -127,39 +107,37 @@ apt update
|
||||
apt install -y mongodb-mongosh
|
||||
EOF
|
||||
|
||||
ENV FERRETDB_POSTGRESQL_URL=postgres://username:password@127.0.0.1:5432/ferretdb
|
||||
ENV POSTGRES_USER=username
|
||||
ENV POSTGRES_PASSWORD=password
|
||||
ENV POSTGRES_DB=ferretdb
|
||||
|
||||
STOPSIGNAL SIGHUP
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
# all-in-one hacks stop there
|
||||
COPY --from=evaluation-build /src/bin/ferretdb /usr/local/bin/ferretdb
|
||||
COPY build/ferretdb/99-start-ferretdb.sh /docker-entrypoint-initdb.d/
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=5s --retries=1 --start-period=30s --start-interval=5s \
|
||||
CMD ["/ferretdb", "ping"]
|
||||
|
||||
WORKDIR /
|
||||
VOLUME /state
|
||||
EXPOSE 27017 27018 8088
|
||||
|
||||
ENV GOCOVERDIR=/tmp/cover
|
||||
ENV GORACE=halt_on_error=1,history_size=2
|
||||
|
||||
ENV POSTGRES_USER=username
|
||||
ENV POSTGRES_PASSWORD=password
|
||||
ENV POSTGRES_DB=postgres
|
||||
|
||||
ENV FERRETDB_POSTGRESQL_URL=postgres://username:password@127.0.0.1:5432/postgres
|
||||
ENV FERRETDB_STATE_DIR=/tmp/state
|
||||
|
||||
# don't forget to update documentation if you change defaults
|
||||
ENV FERRETDB_LISTEN_ADDR=:27017
|
||||
# ENV FERRETDB_LISTEN_TLS=:27018
|
||||
ENV FERRETDB_DEBUG_ADDR=:8088
|
||||
ENV FERRETDB_STATE_DIR=/state
|
||||
ENV FERRETDB_SQLITE_URL=file:/state/
|
||||
|
||||
ARG LABEL_VERSION
|
||||
ARG LABEL_COMMIT
|
||||
|
||||
# TODO https://github.com/FerretDB/FerretDB/issues/2212
|
||||
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative"
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative (evaluation image)"
|
||||
LABEL org.opencontainers.image.revision="${LABEL_COMMIT}"
|
||||
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
|
||||
LABEL org.opencontainers.image.title="FerretDB"
|
||||
LABEL org.opencontainers.image.title="FerretDB (evaluation image)"
|
||||
LABEL org.opencontainers.image.url="https://www.ferretdb.com/"
|
||||
LABEL org.opencontainers.image.vendor="FerretDB Inc."
|
||||
LABEL org.opencontainers.image.version="${LABEL_VERSION}"
|
||||
@@ -14,7 +14,7 @@ ARG LABEL_COMMIT
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23.5 AS production-prepare
|
||||
|
||||
# use a single directory for all Go caches to simpliy RUN --mount commands below
|
||||
# use a single directory for all Go caches to simplify RUN --mount commands below
|
||||
ENV GOPATH=/cache/gopath
|
||||
ENV GOCACHE=/cache/gocache
|
||||
ENV GOMODCACHE=/cache/gomodcache
|
||||
@@ -39,7 +39,6 @@ EOF
|
||||
FROM golang:1.23.5 AS production-build
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
ARG LABEL_VERSION
|
||||
ARG LABEL_COMMIT
|
||||
@@ -70,9 +69,6 @@ git status
|
||||
# because v2+ is problematic for some virtualization platforms and older hardware.
|
||||
export GOAMD64=v1
|
||||
|
||||
# Set GOARM explicitly due to https://github.com/docker-library/golang/issues/494.
|
||||
export GOARM=${TARGETVARIANT#v}
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
go env
|
||||
@@ -108,7 +104,7 @@ USER ferretdb:ferretdb
|
||||
COPY --from=production-build /src/bin/ferretdb /ferretdb
|
||||
COPY --from=production-build --chown=ferretdb:ferretdb /state /state
|
||||
|
||||
ENTRYPOINT ["/ferretdb"]
|
||||
ENTRYPOINT [ "/ferretdb" ]
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=5s --retries=1 --start-period=30s --start-interval=5s \
|
||||
CMD ["/ferretdb", "ping"]
|
||||
@@ -122,14 +118,12 @@ ENV FERRETDB_LISTEN_ADDR=:27017
|
||||
# ENV FERRETDB_LISTEN_TLS=:27018
|
||||
ENV FERRETDB_DEBUG_ADDR=:8088
|
||||
ENV FERRETDB_STATE_DIR=/state
|
||||
ENV FERRETDB_SQLITE_URL=file:/state/
|
||||
|
||||
ARG LABEL_VERSION
|
||||
ARG LABEL_COMMIT
|
||||
|
||||
# TODO https://github.com/FerretDB/FerretDB/issues/2212
|
||||
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative"
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL org.opencontainers.image.revision="${LABEL_COMMIT}"
|
||||
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
|
||||
LABEL org.opencontainers.image.title="FerretDB"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copy to test.js and run with `task testjs`.
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const coll = db.test;
|
||||
|
||||
coll.drop();
|
||||
|
||||
const init = [
|
||||
{ _id: "double", v: 42.13 },
|
||||
{ _id: "double-whole", v: 42.0 },
|
||||
{ _id: "double-zero", v: 0.0 },
|
||||
];
|
||||
|
||||
coll.insertMany(init);
|
||||
|
||||
const query = { v: { $gt: 42.0 } };
|
||||
|
||||
const expected = [{ _id: "double", v: 42.13 }];
|
||||
|
||||
const actual = coll.find(query).toArray();
|
||||
assert.eq(expected, actual);
|
||||
|
||||
print("test.js passed!");
|
||||
})();
|
||||
@@ -3,5 +3,26 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Update the following example with your test.
|
||||
|
||||
const coll = db.test;
|
||||
|
||||
coll.drop();
|
||||
|
||||
const init = [
|
||||
{ _id: "double", v: 42.13 },
|
||||
{ _id: "double-whole", v: 42.0 },
|
||||
{ _id: "double-zero", v: 0.0 },
|
||||
];
|
||||
|
||||
coll.insertMany(init);
|
||||
|
||||
const query = { v: { $gt: 42.0 } };
|
||||
|
||||
const expected = [{ _id: "double", v: 42.13 }];
|
||||
|
||||
const actual = coll.find(query).toArray();
|
||||
assert.eq(expected, actual);
|
||||
|
||||
print("test.js passed!");
|
||||
})();
|
||||
@@ -0,0 +1,13 @@
|
||||
net:
|
||||
port: 47017 # matches host's port for replica set to work
|
||||
bindIpAll: true
|
||||
tls:
|
||||
mode: disabled
|
||||
|
||||
security:
|
||||
authorization: enabled
|
||||
keyFile: /etc/mongod_keyfile.txt
|
||||
|
||||
replication:
|
||||
oplogSizeMB: 512
|
||||
replSetName: rs0
|
||||
+1
-5
@@ -1,12 +1,8 @@
|
||||
net:
|
||||
port: 47017 # matches host's port for replica set to work
|
||||
port: 37017 # matches host's port for replica set to work
|
||||
bindIpAll: true
|
||||
tls:
|
||||
mode: disabled
|
||||
|
||||
security:
|
||||
authorization: disabled
|
||||
|
||||
replication:
|
||||
oplogSizeMB: 512
|
||||
replSetName: rs0
|
||||
@@ -1,16 +0,0 @@
|
||||
net:
|
||||
port: 47018 # matches host's port for replica set to work
|
||||
bindIpAll: true
|
||||
tls:
|
||||
mode: requireTLS
|
||||
certificateKeyFile: /etc/certs/server.pem
|
||||
CAFile: /etc/certs/rootCA-cert.pem
|
||||
clusterFile: /etc/certs/client.pem
|
||||
|
||||
security:
|
||||
authorization: enabled # that also enables authentication
|
||||
clusterAuthMode: x509
|
||||
|
||||
replication:
|
||||
oplogSizeMB: 512
|
||||
replSetName: rs0
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "shared_preload_libraries = 'pg_cron,pg_documentdb_core,pg_documentdb'" >> $PGDATA/postgresql.conf
|
||||
echo "cron.database_name = 'postgres'" >> $PGDATA/postgresql.conf
|
||||
|
||||
source /usr/local/bin/docker-entrypoint.sh
|
||||
docker_temp_server_stop
|
||||
docker_temp_server_start "$@"
|
||||
@@ -0,0 +1 @@
|
||||
CREATE EXTENSION documentdb CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
CREATE EXTENSION pgtap;
|
||||
@@ -0,0 +1,71 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM postgres:16.6 AS development
|
||||
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
|
||||
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt <<EOF
|
||||
set -ex
|
||||
|
||||
apt update
|
||||
apt upgrade -y
|
||||
apt install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
libicu-dev \
|
||||
libkrb5-dev \
|
||||
pkg-config \
|
||||
postgresql-16-cron \
|
||||
postgresql-16-pgvector \
|
||||
postgresql-16-postgis-3 \
|
||||
postgresql-16-rum \
|
||||
postgresql-server-dev-16
|
||||
EOF
|
||||
|
||||
# extra packages for development
|
||||
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt <<EOF
|
||||
set -ex
|
||||
|
||||
apt install -y \
|
||||
bpftrace \
|
||||
gdb \
|
||||
postgresql-16-pgtap
|
||||
EOF
|
||||
|
||||
RUN --mount=target=/src,rw <<EOF
|
||||
set -ex
|
||||
|
||||
cd /src
|
||||
|
||||
mkdir -p /tmp/install_setup
|
||||
cp build/postgres-documentdb/documentdb/scripts/* /tmp/install_setup/
|
||||
cp build/postgres-documentdb/10-preload.sh build/postgres-documentdb/20-install.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
cp build/postgres-documentdb/90-install-development.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
export CLEANUP_SETUP=1
|
||||
export INSTALL_DEPENDENCIES_ROOT=/tmp/install_setup
|
||||
|
||||
env MAKE_PROGRAM=cmake /tmp/install_setup/install_setup_libbson.sh
|
||||
/tmp/install_setup/install_setup_pcre2.sh
|
||||
/tmp/install_setup/install_setup_intel_decimal_math_lib.sh
|
||||
|
||||
cd build/postgres-documentdb/documentdb
|
||||
make -k -j $(nproc) DEBUG=yes
|
||||
make install
|
||||
|
||||
rm -fr /tmp/install_setup /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
WORKDIR /
|
||||
|
||||
LABEL org.opencontainers.image.title="PostgreSQL+DocumentDB (development image)"
|
||||
LABEL org.opencontainers.image.description="PostgreSQL with DocumentDB extension (development image)"
|
||||
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
|
||||
LABEL org.opencontainers.image.url="https://www.ferretdb.com/"
|
||||
LABEL org.opencontainers.image.vendor="FerretDB Inc."
|
||||
Submodule
+1
Submodule build/postgres-documentdb/documentdb added at ae4b8438ab.
@@ -0,0 +1,59 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM postgres:16.6 AS production
|
||||
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LC_CTYPE=en_US.UTF-8
|
||||
|
||||
RUN --mount=type=cache,sharing=locked,target=/var/cache/apt <<EOF
|
||||
set -ex
|
||||
|
||||
apt update
|
||||
apt upgrade -y
|
||||
apt install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
libicu-dev \
|
||||
libkrb5-dev \
|
||||
pkg-config \
|
||||
postgresql-16-cron \
|
||||
postgresql-16-pgvector \
|
||||
postgresql-16-postgis-3 \
|
||||
postgresql-16-rum \
|
||||
postgresql-server-dev-16
|
||||
EOF
|
||||
|
||||
RUN --mount=target=/src,rw <<EOF
|
||||
set -ex
|
||||
|
||||
cd /src
|
||||
|
||||
mkdir -p /tmp/install_setup
|
||||
cp build/postgres-documentdb/documentdb/scripts/* /tmp/install_setup/
|
||||
cp build/postgres-documentdb/10-preload.sh build/postgres-documentdb/20-install.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
export CLEANUP_SETUP=1
|
||||
export INSTALL_DEPENDENCIES_ROOT=/tmp/install_setup
|
||||
|
||||
env MAKE_PROGRAM=cmake /tmp/install_setup/install_setup_libbson.sh
|
||||
/tmp/install_setup/install_setup_pcre2.sh
|
||||
/tmp/install_setup/install_setup_intel_decimal_math_lib.sh
|
||||
|
||||
cd build/postgres-documentdb/documentdb
|
||||
make -k -j $(nproc)
|
||||
make install
|
||||
|
||||
rm -fr /tmp/install_setup /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
WORKDIR /
|
||||
|
||||
LABEL org.opencontainers.image.title="PostgreSQL+DocumentDB"
|
||||
LABEL org.opencontainers.image.description="PostgreSQL with DocumentDB extension"
|
||||
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
|
||||
LABEL org.opencontainers.image.url="https://www.ferretdb.com/"
|
||||
LABEL org.opencontainers.image.vendor="FerretDB Inc."
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Please do not merge changes in this file.
|
||||
|
||||
-- See https://pgtap.org/documentation.html#usingpgtap.
|
||||
|
||||
-- Start transaction and plan the tests.
|
||||
BEGIN;
|
||||
SELECT plan(1); -- number of test assertions
|
||||
|
||||
SET documentdb_core.bsonUseEJson = on;
|
||||
|
||||
-- Update the following example with your test.
|
||||
|
||||
SELECT pass();
|
||||
|
||||
-- Finish the tests and clean up.
|
||||
SELECT * FROM finish();
|
||||
ROLLBACK;
|
||||
@@ -15,3 +15,5 @@ scan:
|
||||
- .env
|
||||
- .envrc
|
||||
- build/certs/*.pem
|
||||
- internal/dataapi/api/openapi.json
|
||||
- internal/dataapi/api/openapi-minimal.json
|
||||
@@ -12,11 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !ferretdb_no_postgresql
|
||||
package version
|
||||
|
||||
package main
|
||||
|
||||
// init adds "postgresql" backend flags.
|
||||
func init() {
|
||||
handlerFlags["postgresql"] = &postgreSQLFlags
|
||||
}
|
||||
// DocumentDB is a version of DocumentDB this version of FerretDB is compatible with.
|
||||
const DocumentDB = "0.100.0 gitref: HEAD sha:ae4b843 buildId:0"
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// runGit runs `git` with given arguments and returns stdout.
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.0.42
|
||||
7.0.77
|
||||
+20
-32
@@ -25,28 +25,18 @@
|
||||
//
|
||||
// # Go build tags
|
||||
//
|
||||
// The following Go build tags (also known as build constraints) affect all builds of FerretDB,
|
||||
// including embedded usage:
|
||||
// The following Go build tags (also known as build constraints) affect builds of FerretDB:
|
||||
//
|
||||
// ferretdb_debug - enables debug build (see below; implied by builds with race detector)
|
||||
// ferretdb_hana - enables Hana backend (alpha)
|
||||
// ferretdb_no_postgresql - disables PostgreSQL backend
|
||||
// ferretdb_no_sqlite - disables SQLite backend
|
||||
// ferretdb_dev - enables development build (see below; implied by builds with race detector)
|
||||
//
|
||||
// # Debug builds
|
||||
// # Development builds
|
||||
//
|
||||
// Debug builds of FerretDB behave differently in a few aspects:
|
||||
// Development builds of FerretDB behave differently in a few aspects:
|
||||
// - Some values that are normally randomized are fixed or less randomized to make debugging easier.
|
||||
// - Some internal errors cause crashes instead of being handled more gracefully.
|
||||
// - Stack traces are collected more liberally.
|
||||
// - Metrics are written to stderr on exit.
|
||||
// - The default logging level is set to debug.
|
||||
//
|
||||
// Debug builds are orthogonal to production releases, development releases, and local/host builds.
|
||||
// For example, the host build could be made non-debug, and the production release, in theory, could be a debug build.
|
||||
//
|
||||
// Currently, our production releases are non-debug builds,
|
||||
// and all other builds and releases (development releases, all-in-one releases, local builds, etc.) are debug builds.
|
||||
package version
|
||||
|
||||
import (
|
||||
@@ -57,13 +47,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/types"
|
||||
"github.com/FerretDB/FerretDB/internal/util/debugbuild"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/devbuild"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
)
|
||||
|
||||
// Each pattern in a //go:embed line must match at least one file or non-empty directory,
|
||||
// but most files are generated and are not present when embeddable FerretDB package is used.
|
||||
// but most files are generated and may be absent.
|
||||
// As a workaround, mongodb.txt is always present.
|
||||
|
||||
//go:generate go run ./generate.go
|
||||
@@ -80,14 +69,14 @@ type Info struct {
|
||||
Branch string
|
||||
Dirty bool
|
||||
Package string
|
||||
DebugBuild bool
|
||||
BuildEnvironment *types.Document
|
||||
DevBuild bool
|
||||
BuildEnvironment map[string]string
|
||||
|
||||
// MongoDBVersion is fake MongoDB version for clients that check major.minor to adjust their behavior.
|
||||
MongoDBVersion string
|
||||
|
||||
// MongoDBVersionArray is MongoDBVersion, but as an array.
|
||||
MongoDBVersionArray *types.Array
|
||||
MongoDBVersionArray [4]int32
|
||||
}
|
||||
|
||||
// info singleton instance set by init().
|
||||
@@ -112,7 +101,7 @@ func init() {
|
||||
minor := must.NotFail(strconv.ParseInt(parts[2], 10, 32))
|
||||
patch := must.NotFail(strconv.ParseInt(parts[3], 10, 32))
|
||||
mongoDBVersion := fmt.Sprintf("%d.%d.%d", major, minor, patch)
|
||||
mongoDBVersionArray := must.NotFail(types.NewArray(int32(major), int32(minor), int32(patch), int32(0)))
|
||||
mongoDBVersionArray := [...]int32{int32(major), int32(minor), int32(patch), int32(0)}
|
||||
|
||||
info = &Info{
|
||||
Version: unknown,
|
||||
@@ -120,22 +109,19 @@ func init() {
|
||||
Branch: unknown,
|
||||
Dirty: false,
|
||||
Package: unknown,
|
||||
DebugBuild: debugbuild.Enabled,
|
||||
BuildEnvironment: must.NotFail(types.NewDocument()),
|
||||
DevBuild: devbuild.Enabled,
|
||||
BuildEnvironment: map[string]string{},
|
||||
MongoDBVersion: mongoDBVersion,
|
||||
MongoDBVersionArray: mongoDBVersionArray,
|
||||
}
|
||||
|
||||
// do not expose extra information when embeddable FerretDB package is used
|
||||
// (and some of it is most likely absent anyway)
|
||||
|
||||
buildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4001
|
||||
if buildInfo.Main.Path != "github.com/FerretDB/FerretDB" {
|
||||
// for tests
|
||||
if buildInfo.Main.Path == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -154,11 +140,11 @@ func init() {
|
||||
msg := "Invalid build/version/version.txt file content. Please run `bin/task gen-version`.\n"
|
||||
msg += "Alternatively, create this file manually with a content similar to\n"
|
||||
msg += "the output of `git describe`: `v<major>.<minor>.<patch>`.\n"
|
||||
msg += "See https://pkg.go.dev/github.com/FerretDB/FerretDB/build/version"
|
||||
msg += "See https://pkg.go.dev/github.com/FerretDB/FerretDB/v2/build/version"
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
info.BuildEnvironment.Set("go.version", buildInfo.GoVersion)
|
||||
info.BuildEnvironment["go.version"] = buildInfo.GoVersion
|
||||
|
||||
for _, s := range buildInfo.Settings {
|
||||
switch s.Key {
|
||||
@@ -179,7 +165,9 @@ func init() {
|
||||
info.Dirty = must.NotFail(strconv.ParseBool(s.Value))
|
||||
|
||||
default:
|
||||
info.BuildEnvironment.Set(s.Key, s.Value)
|
||||
if v := s.Value; v != "" {
|
||||
info.BuildEnvironment[s.Key] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/types"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
@@ -29,6 +25,6 @@ func TestGet(t *testing.T) {
|
||||
|
||||
assert.NotEmpty(t, v.Version)
|
||||
|
||||
assert.Equal(t, "7.0.42", v.MongoDBVersion)
|
||||
testutil.AssertEqual(t, must.NotFail(types.NewArray(int32(7), int32(0), int32(42), int32(0))), v.MongoDBVersionArray)
|
||||
assert.Equal(t, "7.0.77", v.MongoDBVersion)
|
||||
assert.Equal(t, [...]int32{int32(7), int32(0), int32(77), int32(0)}, v.MongoDBVersionArray)
|
||||
}
|
||||
+16
-243
@@ -23,8 +23,6 @@ import (
|
||||
"html/template"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -34,16 +32,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/FerretDB/FerretDB/build/version"
|
||||
mysqlpool "github.com/FerretDB/FerretDB/internal/backends/mysql/metadata/pool"
|
||||
"github.com/FerretDB/FerretDB/internal/backends/postgresql/metadata/pool"
|
||||
"github.com/FerretDB/FerretDB/internal/util/ctxutil"
|
||||
"github.com/FerretDB/FerretDB/internal/util/debug"
|
||||
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/internal/util/logging"
|
||||
"github.com/FerretDB/FerretDB/internal/util/state"
|
||||
"github.com/FerretDB/FerretDB/v2/build/version"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/ctxutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -57,225 +49,6 @@ var (
|
||||
// versionFile contains version information with leading v.
|
||||
const versionFile = "build/version/version.txt"
|
||||
|
||||
// waitForPort waits for the given port to be available until ctx is canceled.
|
||||
func waitForPort(ctx context.Context, logger *slog.Logger, port uint16) error {
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
logger.InfoContext(ctx, fmt.Sprintf("Waiting for %s to be up", addr))
|
||||
|
||||
var retry int64
|
||||
for ctx.Err() == nil {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s: %s", addr, err))
|
||||
|
||||
retry++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, retry)
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to connect to %s", addr)
|
||||
}
|
||||
|
||||
// setupAnyPostgres configures given PostgreSQL.
|
||||
func setupAnyPostgres(ctx context.Context, logger *slog.Logger, uri string) error {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.ParseUint(u.Port(), 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if u.User == nil {
|
||||
return lazyerrors.New("No username specified")
|
||||
}
|
||||
|
||||
if err = waitForPort(ctx, logger, uint16(port)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sp, err := state.NewProvider("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := pool.New(uri, logger, sp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer p.Close()
|
||||
|
||||
username := u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
|
||||
var retry int64
|
||||
for ctx.Err() == nil {
|
||||
if _, err = p.Get(username, password); err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s: %s", uri, err))
|
||||
|
||||
retry++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, retry)
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupPostgres configures `postgres` container.
|
||||
func setupPostgres(ctx context.Context, logger *slog.Logger) error {
|
||||
// user `username` must exist, but password may be any, even empty
|
||||
return setupAnyPostgres(ctx, logging.WithName(logger, "postgres"), "postgres://username@127.0.0.1:5432/ferretdb")
|
||||
}
|
||||
|
||||
// setupPostgresSecured configures `postgres_secured` container.
|
||||
func setupPostgresSecured(ctx context.Context, logger *slog.Logger) error {
|
||||
return setupAnyPostgres(ctx, logging.WithName(logger, "postgres_secured"), "postgres://username:password@127.0.0.1:5433/ferretdb") //nolint:lll // for readability
|
||||
}
|
||||
|
||||
// setupMySQL configures `mysql` container.
|
||||
func setupMySQL(ctx context.Context, logger *slog.Logger) error {
|
||||
uri := "mysql://root:password@127.0.0.1:3306/ferretdb"
|
||||
|
||||
sp, err := state.NewProvider("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = waitForPort(ctx, logging.WithName(logger, "mysql"), 3306); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := mysqlpool.New(uri, logger, sp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer p.Close()
|
||||
|
||||
var retry int64
|
||||
for ctx.Err() == nil {
|
||||
db, err := p.Get("root", "password")
|
||||
if err == nil {
|
||||
if _, err = db.ExecContext(ctx, "GRANT ALL PRIVILEGES ON *.* TO 'username'@'%';"); err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s: %s", uri, err))
|
||||
|
||||
retry++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, retry)
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupMongodb configures `mongodb` container.
|
||||
func setupMongodb(ctx context.Context, logger *slog.Logger) error {
|
||||
if err := waitForPort(ctx, logging.WithName(logger, "mongodb"), 47017); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eval := `'rs.initiate({_id: "rs0", members: [{_id: 0, host: "localhost:47017" }]})'`
|
||||
args := []string{"compose", "exec", "-T", "mongodb", "mongosh", "--port=47017", "--eval", eval}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var retry int64
|
||||
|
||||
for ctx.Err() == nil {
|
||||
buf.Reset()
|
||||
|
||||
err := runCommand("docker", args, &buf, logger)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s:\n%s", err, buf.String()))
|
||||
|
||||
retry++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, retry)
|
||||
}
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// setupMongodbSecured configures `mongodb_secured` container.
|
||||
func setupMongodbSecured(ctx context.Context, logger *slog.Logger) error {
|
||||
if err := waitForPort(ctx, logging.WithName(logger, "mongodb_secured"), 47018); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eval := `'rs.initiate({_id: "rs0", members: [{_id: 0, host: "localhost:47018" }]})'`
|
||||
shell := `mongodb://username:password@127.0.0.1:47018/?tls=true&tlsCertificateKeyFile=/etc/certs/client.pem&tlsCaFile=/etc/certs/rootCA-cert.pem` //nolint:lll // for readability
|
||||
args := []string{"compose", "exec", "-T", "mongodb_secured", "mongosh", "--eval", eval, "--shell", shell}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var retry int64
|
||||
|
||||
for ctx.Err() == nil {
|
||||
buf.Reset()
|
||||
|
||||
err := runCommand("docker", args, &buf, logger)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s:\n%s", err, buf.String()))
|
||||
|
||||
retry++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, retry)
|
||||
}
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// setup runs all setup commands.
|
||||
func setup(ctx context.Context, logger *slog.Logger) error {
|
||||
h, err := debug.Listen(&debug.ListenOpts{
|
||||
TCPAddr: "127.0.0.1:8089",
|
||||
L: logging.WithName(logger, "debug"),
|
||||
R: prometheus.DefaultRegisterer,
|
||||
})
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
go h.Serve(ctx)
|
||||
|
||||
for _, f := range []func(context.Context, *slog.Logger) error{
|
||||
setupPostgres,
|
||||
setupPostgresSecured,
|
||||
setupMySQL,
|
||||
setupMongodb,
|
||||
setupMongodbSecured,
|
||||
} {
|
||||
if err := f(ctx, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// runCommand runs command with given arguments.
|
||||
func runCommand(command string, args []string, stdout io.Writer, logger *slog.Logger) error {
|
||||
bin, err := exec.LookPath(command)
|
||||
@@ -338,12 +111,12 @@ func printDiagnosticData(w io.Writer, setupError error, logger *slog.Logger) err
|
||||
"GOOS": runtime.GOOS,
|
||||
"GOARCH": runtime.GOARCH,
|
||||
|
||||
"Version": info.Version,
|
||||
"Commit": info.Commit,
|
||||
"Branch": info.Branch,
|
||||
"Dirty": info.Dirty,
|
||||
"Package": info.Package,
|
||||
"DebugBuild": info.DebugBuild,
|
||||
"Version": info.Version,
|
||||
"Commit": info.Commit,
|
||||
"Branch": info.Branch,
|
||||
"Dirty": info.Dirty,
|
||||
"Package": info.Package,
|
||||
"DevBuild": info.DevBuild,
|
||||
|
||||
"GoVersion": runtime.Version(),
|
||||
"GitVersion": strings.TrimSpace(gitVersion),
|
||||
@@ -417,6 +190,7 @@ type TestsRunParams struct {
|
||||
ShardTotal uint `help:"Total number of shards."`
|
||||
Run string `help:"Run only tests matching the regexp."`
|
||||
Skip string `help:"Skip tests matching the regexp."`
|
||||
RawPrefix string `help:"Prefix for raw output files."`
|
||||
|
||||
Args []string `arg:"" help:"Other arguments and flags for 'go test'." passthrough:""`
|
||||
}
|
||||
@@ -434,13 +208,13 @@ var cli struct {
|
||||
|
||||
Shell struct {
|
||||
Mkdir struct {
|
||||
Paths []string `arg:"" name:"path" help:"Paths to create." type:"path"`
|
||||
Paths []string `name:"path" arg:"" help:"Paths to create." type:"path"`
|
||||
} `cmd:"" help:"Create directories if they do not already exist."`
|
||||
Rmdir struct {
|
||||
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
|
||||
Paths []string `name:"path" arg:"" help:"Paths to remove." type:"path"`
|
||||
} `cmd:"" help:"Remove directories."`
|
||||
Read struct {
|
||||
Paths []string `arg:"" name:"path" help:"Paths to read." type:"path"`
|
||||
Paths []string `name:"path" arg:"" help:"Paths to read." type:"path"`
|
||||
} `cmd:"" help:"Read files."`
|
||||
} `cmd:""`
|
||||
|
||||
@@ -457,10 +231,10 @@ var cli struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
kongCtx := kong.Parse(&cli)
|
||||
kongCtx := kong.Parse(&cli, kong.DefaultEnvars("ENVTOOL"))
|
||||
|
||||
// https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
|
||||
if t, _ := strconv.ParseBool(os.Getenv("CI")); t {
|
||||
if t, _ := strconv.ParseBool(os.Getenv("RUNNER_DEBUG")); t {
|
||||
cli.Debug = true
|
||||
}
|
||||
|
||||
@@ -475,7 +249,6 @@ func main() {
|
||||
RemoveTime: true,
|
||||
RemoveSource: true,
|
||||
}
|
||||
|
||||
logging.Setup(opts, "")
|
||||
logger := slog.Default()
|
||||
|
||||
@@ -562,7 +335,7 @@ func main() {
|
||||
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
logger.Error("Failed to exit", logging.Error(exitErr))
|
||||
logger.Error("Failed", logging.Error(exitErr))
|
||||
os.Exit(exitErr.ExitCode())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
|
||||
)
|
||||
|
||||
func TestPrintDiagnosticData(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ Version: {{.Version}}
|
||||
Commit: {{.Commit}}
|
||||
Branch: {{.Branch}}
|
||||
Dirty: {{.Dirty}}
|
||||
Debug: {{.DebugBuild}}
|
||||
Dev: {{.DevBuild}}
|
||||
|
||||
{{.GoVersion}}
|
||||
{{.GitVersion}}
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
)
|
||||
|
||||
// copyFile copies a file from src to dst, overwriting dst if it exists.
|
||||
@@ -68,7 +68,7 @@ func copyFile(src, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// fuzzGeneratedCorpus returns $GOCACHE/fuzz/github.com/FerretDB/FerretDB,
|
||||
// fuzzGeneratedCorpus returns $GOCACHE/fuzz/github.com/FerretDB/FerretDB/v2,
|
||||
// ensuring that this directory exists.
|
||||
func fuzzGeneratedCorpus() (string, error) {
|
||||
b, err := exec.Command("go", "env", "GOCACHE").Output()
|
||||
@@ -76,7 +76,7 @@ func fuzzGeneratedCorpus() (string, error) {
|
||||
return "", lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
path := filepath.Join(string(bytes.TrimSpace(b)), "fuzz", "github.com", "FerretDB", "FerretDB")
|
||||
path := filepath.Join(string(bytes.TrimSpace(b)), "fuzz", "github.com", "FerretDB", "FerretDB", "v2")
|
||||
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// setupMongoDB configures MongoDB containers.
|
||||
func setupMongoDB(ctx context.Context, logger *slog.Logger, uri, name string) error {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
logger = logging.WithName(logger, name)
|
||||
|
||||
eval := fmt.Sprintf(`'rs.initiate({_id: "rs0", members: [{_id: 0, host: "localhost:%s" }]})'`, port)
|
||||
args := []string{"compose", "exec", "-T", name, "mongosh", "--eval", eval, uri}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var attempt int64
|
||||
|
||||
for ctx.Err() == nil {
|
||||
buf.Reset()
|
||||
|
||||
err := runCommand("docker", args, &buf, logger)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, fmt.Sprintf("%s:\n%s", err, buf.String()))
|
||||
|
||||
attempt++
|
||||
ctxutil.SleepWithJitter(ctx, time.Second, attempt)
|
||||
}
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// setup runs all setup commands.
|
||||
func setup(ctx context.Context, logger *slog.Logger) error {
|
||||
h, err := debug.Listen(&debug.ListenOpts{
|
||||
TCPAddr: "127.0.0.1:8089",
|
||||
L: logging.WithName(logger, "debug"),
|
||||
R: prometheus.DefaultRegisterer,
|
||||
})
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
go h.Serve(ctx)
|
||||
|
||||
if err = setupMongoDB(ctx, logger, "mongodb://username:password@127.0.0.1:47017/", "mongodb-secure"); err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
logger.InfoContext(ctx, "Done.")
|
||||
return nil
|
||||
}
|
||||
+173
-64
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
@@ -34,11 +35,10 @@ import (
|
||||
otelattribute "go.opentelemetry.io/otel/attribute"
|
||||
otelcodes "go.opentelemetry.io/otel/codes"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/observability"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/lazyerrors"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
|
||||
)
|
||||
|
||||
// testEvent represents a single even emitted by `go test -json`.
|
||||
@@ -69,15 +69,18 @@ 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(dir, re string, logger *slog.Logger) ([]string, error) {
|
||||
func listTestFuncs(ctx context.Context, dir, re string, logger *slog.Logger) ([]string, error) {
|
||||
ctx, span := otel.Tracer("").Start(ctx, "listTestFuncs")
|
||||
defer span.End()
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
cmd := exec.Command("go", "test", "-list="+re, "./...")
|
||||
cmd := exec.CommandContext(ctx, "go", "test", "-list="+re, "./...")
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
logger.Info(fmt.Sprintf("Running %s", strings.Join(cmd.Args, " ")))
|
||||
logger.InfoContext(ctx, fmt.Sprintf("Running %s", strings.Join(cmd.Args, " ")))
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, lazyerrors.Error(err)
|
||||
@@ -114,10 +117,7 @@ func listTestFuncs(dir, re string, logger *slog.Logger) ([]string, error) {
|
||||
return nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
res := maps.Keys(testFuncs)
|
||||
slices.Sort(res)
|
||||
|
||||
return res, nil
|
||||
return slices.Sorted(maps.Keys(testFuncs)), nil
|
||||
}
|
||||
|
||||
// shardTestFuncs shards given top-level test functions.
|
||||
@@ -155,7 +155,10 @@ 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(dir string, index, total uint, run, skip string, logger *slog.Logger) ([]string, uint, error) {
|
||||
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")
|
||||
defer span.End()
|
||||
|
||||
listRE := "."
|
||||
|
||||
if run != "" || skip != "" {
|
||||
@@ -181,7 +184,7 @@ func testArgs(dir string, index, total uint, run, skip string, logger *slog.Logg
|
||||
listRE = run
|
||||
}
|
||||
|
||||
testFuncs, err := listTestFuncs(dir, listRE, logger)
|
||||
testFuncs, err := listTestFuncs(ctx, dir, listRE, logger)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -216,24 +219,111 @@ func resultKey(packageName, testName string) string {
|
||||
return packageName + "." + testName
|
||||
}
|
||||
|
||||
// runGoTest runs `go test` with given extra args.
|
||||
func runGoTest(runCtx context.Context, args []string, total uint, times bool, logger *slog.Logger) error {
|
||||
cmd := exec.CommandContext(runCtx, "go", append([]string{"test", "-json"}, args...)...)
|
||||
// startGoTest starts `go test` with given arguments.
|
||||
func startGoTest(ctx context.Context, args []string, raw string, l *slog.Logger) (*json.Decoder, func() error, error) {
|
||||
var cleanups []func() error
|
||||
|
||||
logger.InfoContext(runCtx, fmt.Sprintf("Running %s", strings.Join(cmd.Args, " ")))
|
||||
cleanup := func() error {
|
||||
var err error
|
||||
|
||||
for _, f := range slices.Backward(cleanups) {
|
||||
if e := f(); e != nil {
|
||||
l.DebugContext(ctx, fmt.Sprintf("Cleanup error: %s", e))
|
||||
|
||||
if err == nil {
|
||||
err = lazyerrors.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "go", append([]string{"test", "-json"}, args...)...)
|
||||
|
||||
l.InfoContext(ctx, fmt.Sprintf("Running %s", strings.Join(cmd.Args, " ")))
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
p, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
if raw != "" {
|
||||
f, e := os.Create(raw + "-stderr.txt.tmp")
|
||||
if e != nil {
|
||||
_ = cleanup()
|
||||
return nil, nil, lazyerrors.Error(e)
|
||||
}
|
||||
|
||||
cleanups = append(cleanups, f.Close)
|
||||
|
||||
cmd.Stderr = io.MultiWriter(f, os.Stderr)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
_ = cleanup()
|
||||
return nil, nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
d := json.NewDecoder(stdout)
|
||||
|
||||
if raw != "" {
|
||||
f, e := os.Create(raw + "-stdout.json.tmp")
|
||||
if e != nil {
|
||||
_ = cleanup()
|
||||
return nil, nil, lazyerrors.Error(e)
|
||||
}
|
||||
|
||||
cleanups = append(cleanups, f.Close)
|
||||
|
||||
d = json.NewDecoder(io.TeeReader(stdout, f))
|
||||
}
|
||||
|
||||
d.DisallowUnknownFields()
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
_ = cleanup()
|
||||
return nil, nil, lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
defer cmd.Cancel() //nolint:errcheck // safe to ignore
|
||||
cleanups = append(cleanups, cmd.Wait)
|
||||
|
||||
return d, cleanup, nil
|
||||
}
|
||||
|
||||
// runGoTestOpts represents [runGoTest] options.
|
||||
//
|
||||
//nolint:vet // for readability
|
||||
type runGoTestOpts struct {
|
||||
args []string
|
||||
total uint
|
||||
times bool
|
||||
rawPrefix string
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// runGoTest runs `go test` with given extra args.
|
||||
func runGoTest(runCtx context.Context, opts *runGoTestOpts) (resErr error) {
|
||||
totalTests := "?"
|
||||
if opts.total > 0 {
|
||||
totalTests = strconv.Itoa(int(opts.total))
|
||||
}
|
||||
|
||||
runCtx, runSpan := otel.Tracer("").Start(runCtx, "run")
|
||||
runSpan.SetAttributes(otelattribute.String("db.ferretdb.envtool.total_tests", totalTests))
|
||||
defer runSpan.End()
|
||||
|
||||
d, cleanup, err := startGoTest(runCtx, opts.args, opts.rawPrefix, opts.logger)
|
||||
if err != nil {
|
||||
resErr = lazyerrors.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if e := cleanup(); resErr == nil && e != nil {
|
||||
resErr = lazyerrors.Error(e)
|
||||
}
|
||||
}()
|
||||
|
||||
runSpan.AddEvent("started")
|
||||
|
||||
// Keys are:
|
||||
// - "package/name"
|
||||
@@ -244,29 +334,24 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
results := make(map[string]*testResult, 300)
|
||||
|
||||
var done int
|
||||
|
||||
d := json.NewDecoder(p)
|
||||
d.DisallowUnknownFields()
|
||||
|
||||
totalTests := "?"
|
||||
if total > 0 {
|
||||
totalTests = strconv.Itoa(int(total))
|
||||
}
|
||||
|
||||
runCtx, runSpan := otel.Tracer("").Start(runCtx, "run")
|
||||
runSpan.SetAttributes(otelattribute.String("db.ferretdb.envtool.total_tests", totalTests))
|
||||
defer runSpan.End()
|
||||
var firstEvent bool
|
||||
|
||||
for {
|
||||
var event testEvent
|
||||
if err = d.Decode(&event); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return lazyerrors.Error(err)
|
||||
resErr = lazyerrors.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if !firstEvent {
|
||||
runSpan.AddEvent("first event")
|
||||
firstEvent = true
|
||||
}
|
||||
|
||||
must.NotBeZero(event.Package)
|
||||
|
||||
key := resultKey(event.Package, event.Test)
|
||||
@@ -287,7 +372,9 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
|
||||
// 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))
|
||||
panic(fmt.Sprintf(
|
||||
"no parent test found: package=%q, test=%q, key=%q", event.Package, event.Test, key,
|
||||
))
|
||||
}
|
||||
|
||||
parentCtx = parent.ctx
|
||||
@@ -337,7 +424,7 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
|
||||
// initial setup output or early panic
|
||||
if event.Test == "" {
|
||||
logger.InfoContext(runCtx, out)
|
||||
opts.logger.InfoContext(runCtx, out)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -362,7 +449,7 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
testSpan.End()
|
||||
|
||||
if event.Test == "" {
|
||||
logger.InfoContext(runCtx, strings.ToTitle(event.Action)+" "+event.Package)
|
||||
opts.logger.InfoContext(runCtx, strings.ToTitle(event.Action)+" "+event.Package)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -373,7 +460,7 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
|
||||
msg := strings.ToTitle(event.Action) + " " + event.Test
|
||||
|
||||
if times {
|
||||
if opts.times {
|
||||
msg += fmt.Sprintf(" (%.2fs", event.Time.Sub(res.cont).Seconds())
|
||||
|
||||
if res.run != res.cont {
|
||||
@@ -392,22 +479,29 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
msg += fmt.Sprintf(" %d/%s", done, totalTests)
|
||||
}
|
||||
|
||||
logOutput := opts.logger.WarnContext
|
||||
|
||||
if event.Action == "pass" {
|
||||
logger.InfoContext(runCtx, msg)
|
||||
continue
|
||||
if !opts.logger.Enabled(runCtx, slog.LevelDebug) {
|
||||
opts.logger.InfoContext(runCtx, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
logOutput = opts.logger.DebugContext
|
||||
}
|
||||
|
||||
msg += ":"
|
||||
logger.WarnContext(runCtx, msg)
|
||||
logOutput(runCtx, msg)
|
||||
|
||||
for _, l := range res.outputs {
|
||||
logger.WarnContext(runCtx, l)
|
||||
logOutput(runCtx, l)
|
||||
}
|
||||
|
||||
logger.WarnContext(runCtx, "")
|
||||
logOutput(runCtx, "")
|
||||
|
||||
default:
|
||||
return lazyerrors.Errorf("unknown action %q", event.Action)
|
||||
resErr = lazyerrors.Errorf("unknown action %q", event.Action)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,20 +517,20 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
}
|
||||
|
||||
if unfinished == nil {
|
||||
return cmd.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
slices.Sort(unfinished)
|
||||
|
||||
logger.ErrorContext(runCtx, "")
|
||||
opts.logger.ErrorContext(runCtx, "")
|
||||
|
||||
logger.ErrorContext(runCtx, "Some tests did not finish:")
|
||||
opts.logger.ErrorContext(runCtx, "Some tests did not finish:")
|
||||
|
||||
for _, t := range unfinished {
|
||||
logger.ErrorContext(runCtx, fmt.Sprintf(" %s", t))
|
||||
opts.logger.ErrorContext(runCtx, fmt.Sprintf(" %s", t))
|
||||
}
|
||||
|
||||
logger.ErrorContext(runCtx, "")
|
||||
opts.logger.ErrorContext(runCtx, "")
|
||||
|
||||
// On panic, the last event will not be "fail"; see https://github.com/golang/go/issues/38382.
|
||||
// Try to provide the best possible output in that case.
|
||||
@@ -462,16 +556,16 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
continue
|
||||
}
|
||||
|
||||
logger.ErrorContext(runCtx, fmt.Sprintf("%s:", t))
|
||||
opts.logger.ErrorContext(runCtx, fmt.Sprintf("%s:", t))
|
||||
|
||||
for _, l := range results[t].outputs {
|
||||
logger.ErrorContext(runCtx, l)
|
||||
opts.logger.ErrorContext(runCtx, l)
|
||||
}
|
||||
|
||||
logger.ErrorContext(runCtx, "")
|
||||
opts.logger.ErrorContext(runCtx, "")
|
||||
}
|
||||
|
||||
return cmd.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// testsRun runs tests specified by the shard index and total or by the run regex
|
||||
@@ -479,13 +573,6 @@ func runGoTest(runCtx context.Context, args []string, total uint, times bool, lo
|
||||
func testsRun(ctx context.Context, params *TestsRunParams, logger *slog.Logger) error {
|
||||
logger.DebugContext(ctx, fmt.Sprintf("testsRun: %+v", params))
|
||||
|
||||
args, total, err := testArgs("", params.ShardIndex, params.ShardTotal, params.Run, params.Skip, logger)
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
args = append(args, params.Args...)
|
||||
|
||||
ot, err := observability.NewOTelTraceExporter(&observability.OTelTraceExporterOpts{
|
||||
Logger: logger,
|
||||
Service: "envtool-tests",
|
||||
@@ -495,6 +582,8 @@ func testsRun(ctx context.Context, params *TestsRunParams, logger *slog.Logger)
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
ctx, span := otel.Tracer("").Start(ctx, "testsRun")
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
done := make(chan struct{})
|
||||
|
||||
@@ -503,10 +592,30 @@ func testsRun(ctx context.Context, params *TestsRunParams, logger *slog.Logger)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
err = runGoTest(ctx, args, total, true, logger)
|
||||
defer func() {
|
||||
span.End()
|
||||
cancel()
|
||||
<-done
|
||||
}()
|
||||
|
||||
cancel()
|
||||
<-done
|
||||
args, total, err := testArgs(ctx, "", params.ShardIndex, params.ShardTotal, params.Run, params.Skip, logger)
|
||||
if err != nil {
|
||||
return lazyerrors.Error(err)
|
||||
}
|
||||
|
||||
if len(params.Args) > 0 && params.Args[0] == "--" {
|
||||
params.Args = params.Args[1:]
|
||||
}
|
||||
|
||||
args = append(args, params.Args...)
|
||||
|
||||
err = runGoTest(ctx, &runGoTestOpts{
|
||||
args: args,
|
||||
total: total,
|
||||
times: true,
|
||||
rawPrefix: params.RawPrefix,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
+56
-31
@@ -16,7 +16,6 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
@@ -26,8 +25,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/logging"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -81,7 +80,9 @@ func bufLogger() (*slog.Logger, *bytes.Buffer) {
|
||||
func TestTestArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
list, listErr := listTestFuncs("testdata", ".", testutil.Logger(t))
|
||||
ctx := testutil.Ctx(t)
|
||||
|
||||
list, listErr := listTestFuncs(ctx, "testdata", ".", testutil.Logger(t))
|
||||
require.NoError(t, listErr)
|
||||
require.Equal(
|
||||
t,
|
||||
@@ -92,7 +93,7 @@ func TestTestArgs(t *testing.T) {
|
||||
t.Run("All", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual, total, err := testArgs("testdata", 0, 0, "", "", testutil.Logger(t))
|
||||
actual, total, err := testArgs(ctx, "testdata", 0, 0, "", "", testutil.Logger(t))
|
||||
require.NoError(t, err)
|
||||
expected := []string{"-run=^(TestError1|TestError2|TestNormal1|TestNormal2|TestPanic1|TestSkip1|TestWithSubtest)$"}
|
||||
assert.Equal(t, expected, actual)
|
||||
@@ -102,7 +103,7 @@ func TestTestArgs(t *testing.T) {
|
||||
t.Run("Run", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual, total, err := testArgs("testdata", 0, 0, "(?i)Normal", "", testutil.Logger(t))
|
||||
actual, total, err := testArgs(ctx, "testdata", 0, 0, "(?i)Normal", "", testutil.Logger(t))
|
||||
require.NoError(t, err)
|
||||
expected := []string{"-run=^(TestNormal1|TestNormal2)$"}
|
||||
assert.Equal(t, expected, actual)
|
||||
@@ -112,7 +113,7 @@ func TestTestArgs(t *testing.T) {
|
||||
t.Run("Subtest", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual, total, err := testArgs("testdata", 0, 0, "TestWithSubtest/Second", "", testutil.Logger(t))
|
||||
actual, total, err := testArgs(ctx, "testdata", 0, 0, "TestWithSubtest/Second", "", testutil.Logger(t))
|
||||
require.NoError(t, err)
|
||||
expected := []string{"-run=TestWithSubtest/Second"}
|
||||
assert.Equal(t, expected, actual)
|
||||
@@ -122,7 +123,7 @@ func TestTestArgs(t *testing.T) {
|
||||
t.Run("Shard", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual, total, err := testArgs("testdata", 1, 2, "", "", testutil.Logger(t))
|
||||
actual, total, err := testArgs(ctx, "testdata", 1, 2, "", "", testutil.Logger(t))
|
||||
require.NoError(t, err)
|
||||
expected := []string{"-run=^(TestError1|TestNormal1|TestPanic1|TestWithSubtest)$"}
|
||||
assert.Equal(t, expected, actual)
|
||||
@@ -133,14 +134,18 @@ func TestTestArgs(t *testing.T) {
|
||||
func TestRunGoTest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.TODO()
|
||||
ctx := testutil.Ctx(t)
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestNormal"}, 2, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestNormal"},
|
||||
total: 2,
|
||||
logger: l,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
@@ -148,8 +153,8 @@ func TestRunGoTest(t *testing.T) {
|
||||
"INFO PASS TestNormal1 1/2",
|
||||
"INFO PASS TestNormal2 2/2",
|
||||
"INFO PASS",
|
||||
"INFO ok github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO ok github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
}
|
||||
|
||||
actual := toLogLines(buf)
|
||||
@@ -161,15 +166,19 @@ func TestRunGoTest(t *testing.T) {
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestWithSubtest/Third"}, 1, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestWithSubtest/Third"},
|
||||
total: 1,
|
||||
logger: l,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
"INFO Running go test -json ./testdata -count=1 -run=TestWithSubtest/Third",
|
||||
"INFO PASS TestWithSubtest 1/1",
|
||||
"INFO PASS",
|
||||
"INFO ok github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO ok github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
}
|
||||
|
||||
actual := toLogLines(buf)
|
||||
@@ -181,7 +190,11 @@ func TestRunGoTest(t *testing.T) {
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestWithSubtest/None"}, 1, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestWithSubtest/None"},
|
||||
total: 1,
|
||||
logger: l,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
@@ -189,8 +202,8 @@ func TestRunGoTest(t *testing.T) {
|
||||
"INFO PASS TestWithSubtest 1/1",
|
||||
"INFO testing: warning: no tests to run",
|
||||
"INFO PASS",
|
||||
"INFO ok github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s [no tests to run]",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO ok github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s [no tests to run]",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
}
|
||||
|
||||
actual := toLogLines(buf)
|
||||
@@ -202,7 +215,11 @@ func TestRunGoTest(t *testing.T) {
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestError"}, 2, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestError"},
|
||||
total: 2,
|
||||
logger: l,
|
||||
})
|
||||
|
||||
var exitErr *exec.ExitError
|
||||
require.ErrorAs(t, err, &exitErr)
|
||||
@@ -236,8 +253,8 @@ func TestRunGoTest(t *testing.T) {
|
||||
"WARN --- FAIL: TestError2 (<SEC>s)",
|
||||
"WARN ",
|
||||
"INFO FAIL",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
}
|
||||
|
||||
actual := toLogLines(buf)
|
||||
@@ -249,7 +266,11 @@ func TestRunGoTest(t *testing.T) {
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestSkip"}, 1, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestSkip"},
|
||||
total: 1,
|
||||
logger: l,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
@@ -261,8 +282,8 @@ func TestRunGoTest(t *testing.T) {
|
||||
"WARN --- SKIP: TestSkip1 (<SEC>s)",
|
||||
"WARN ",
|
||||
"INFO PASS",
|
||||
"INFO ok github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO ok github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s",
|
||||
"INFO PASS github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
}
|
||||
|
||||
actual := toLogLines(buf)
|
||||
@@ -274,25 +295,29 @@ func TestRunGoTest(t *testing.T) {
|
||||
|
||||
l, buf := bufLogger()
|
||||
|
||||
err := runGoTest(ctx, []string{"./testdata", "-count=1", "-run=TestPanic"}, 1, false, l)
|
||||
err := runGoTest(ctx, &runGoTestOpts{
|
||||
args: []string{"./testdata", "-count=1", "-run=TestPanic"},
|
||||
total: 1,
|
||||
logger: l,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
expected := []string{
|
||||
"INFO Running go test -json ./testdata -count=1 -run=TestPanic",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/cmd/envtool/testdata <SEC>s",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/cmd/envtool/testdata",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata <SEC>s",
|
||||
"INFO FAIL github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata",
|
||||
"ERROR ",
|
||||
"ERROR Some tests did not finish:",
|
||||
"ERROR github.com/FerretDB/FerretDB/cmd/envtool/testdata.TestPanic1",
|
||||
"ERROR github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata.TestPanic1",
|
||||
"ERROR ",
|
||||
"ERROR github.com/FerretDB/FerretDB/cmd/envtool/testdata.TestPanic1:",
|
||||
"ERROR github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata.TestPanic1:",
|
||||
"ERROR === RUN TestPanic1",
|
||||
"ERROR panic: Panic 1",
|
||||
"ERROR ",
|
||||
"ERROR goroutine <ID> [running]:",
|
||||
"ERROR github.com/FerretDB/FerretDB/cmd/envtool/testdata.TestPanic1.func1()",
|
||||
"ERROR github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata.TestPanic1.func1()",
|
||||
"ERROR <DIR>/cmd/envtool/testdata/panic_test.go:25 <ADDR>",
|
||||
"ERROR created by github.com/FerretDB/FerretDB/cmd/envtool/testdata.TestPanic1 in goroutine <ID>",
|
||||
"ERROR created by github.com/FerretDB/FerretDB/v2/cmd/envtool/testdata.TestPanic1 in goroutine <ID>",
|
||||
"ERROR <DIR>/cmd/envtool/testdata/panic_test.go:23 <ADDR>",
|
||||
"ERROR",
|
||||
}
|
||||
|
||||
+151
-212
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -33,19 +34,20 @@ import (
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
_ "golang.org/x/crypto/x509roots/fallback" // register root TLS certificates for production Docker image
|
||||
|
||||
"github.com/FerretDB/FerretDB/build/version"
|
||||
"github.com/FerretDB/FerretDB/internal/clientconn"
|
||||
"github.com/FerretDB/FerretDB/internal/clientconn/connmetrics"
|
||||
"github.com/FerretDB/FerretDB/internal/handler/registry"
|
||||
"github.com/FerretDB/FerretDB/internal/util/ctxutil"
|
||||
"github.com/FerretDB/FerretDB/internal/util/debug"
|
||||
"github.com/FerretDB/FerretDB/internal/util/debugbuild"
|
||||
"github.com/FerretDB/FerretDB/internal/util/logging"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/observability"
|
||||
"github.com/FerretDB/FerretDB/internal/util/password"
|
||||
"github.com/FerretDB/FerretDB/internal/util/state"
|
||||
"github.com/FerretDB/FerretDB/internal/util/telemetry"
|
||||
"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/util/ctxutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/debug"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/devbuild"
|
||||
"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/state"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/telemetry"
|
||||
)
|
||||
|
||||
// The cli struct represents all command-line commands, fields and flags.
|
||||
@@ -57,19 +59,20 @@ var cli struct {
|
||||
Run struct{} `cmd:"" default:"1" hidden:""`
|
||||
Ping struct{} `cmd:"" help:"Ping existing FerretDB instance."`
|
||||
|
||||
Version bool `default:"false" help:"Print version to stdout and exit." env:"-"`
|
||||
Handler string `default:"postgresql" help:"${help_handler}"`
|
||||
Mode string `default:"${default_mode}" help:"${help_mode}" enum:"${enum_mode}"`
|
||||
Version bool `default:"false" help:"Print version to stdout and exit." env:"-"`
|
||||
Mode string `default:"${default_mode}" help:"${help_mode}" enum:"${enum_mode}"`
|
||||
Auth bool `default:"true" help:"Enable authentication (on by default)." negatable:""`
|
||||
StateDir string `default:"." help:"Process state directory."`
|
||||
ReplSetName string `default:"" help:"Replica set name."`
|
||||
|
||||
Listen struct {
|
||||
Addr string `default:"127.0.0.1:27017" help:"Listen TCP address."`
|
||||
Unix string `default:"" help:"Listen Unix domain socket path."`
|
||||
TLS string `default:"" help:"Listen TLS address."`
|
||||
Addr string `default:"127.0.0.1:27017" help:"Listen TCP address for MongoDB protocol."`
|
||||
Unix string `default:"" help:"Listen Unix domain socket path for MongoDB protocol."`
|
||||
TLS string `default:"" help:"Listen TLS address for MongoDB protocol."`
|
||||
TLSCertFile string `default:"" help:"TLS cert file path."`
|
||||
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."`
|
||||
} `embed:"" prefix:"listen-"`
|
||||
|
||||
Proxy struct {
|
||||
@@ -79,17 +82,7 @@ var cli struct {
|
||||
TLSCaFile string `default:"" help:"Proxy TLS CA file path."`
|
||||
} `embed:"" prefix:"proxy-"`
|
||||
|
||||
DebugAddr string `default:"127.0.0.1:8088" help:"Listen address for HTTP handlers for metrics, profiling, etc."`
|
||||
|
||||
// see setCLIPlugins
|
||||
kong.Plugins
|
||||
|
||||
Setup struct {
|
||||
Database string `default:"" help:"Setup database during backend initialization."`
|
||||
Username string `default:"" help:"Setup user during backend initialization."`
|
||||
Password string `default:"" help:"Setup user's password."`
|
||||
Timeout time.Duration `default:"30s" help:"Setup timeout."`
|
||||
} `embed:"" prefix:"setup-"`
|
||||
DebugAddr string `default:"127.0.0.1:8088" help:"Listen address for HTTP handlers for metrics, pprof, etc."`
|
||||
|
||||
Log struct {
|
||||
Level string `default:"${default_log_level}" help:"${help_log_level}"`
|
||||
@@ -97,6 +90,8 @@ var cli struct {
|
||||
UUID bool `default:"false" help:"Add instance UUID to all log messages." negatable:""`
|
||||
} `embed:"" prefix:"log-"`
|
||||
|
||||
PostgreSQLURL string `name:"postgresql-url" default:"postgres://127.0.0.1:5432/postgres" help:"PostgreSQL URL."`
|
||||
|
||||
MetricsUUID bool `default:"false" help:"Add instance UUID to all metrics." negatable:""`
|
||||
|
||||
OTel struct {
|
||||
@@ -105,86 +100,21 @@ var cli struct {
|
||||
} `embed:"" prefix:"traces-"`
|
||||
} `embed:"" prefix:"otel-"`
|
||||
|
||||
Telemetry telemetry.Flag `default:"undecided" help:"Enable or disable basic telemetry. See https://beacon.ferretdb.com."`
|
||||
Telemetry telemetry.Flag `default:"undecided" help:"${help_telemetry}"`
|
||||
|
||||
Test struct {
|
||||
RecordsDir string `default:"" help:"Testing: directory for record files."`
|
||||
|
||||
DisablePushdown bool `default:"false" help:"Experimental: disable pushdown."`
|
||||
EnableNestedPushdown bool `default:"false" help:"Experimental: enable pushdown for dot notation."`
|
||||
|
||||
CappedCleanup struct {
|
||||
Interval time.Duration `default:"1m" help:"Experimental: capped collections cleanup interval."`
|
||||
Percentage uint8 `default:"10" help:"Experimental: percentage of documents to cleanup."`
|
||||
} `embed:"" prefix:"capped-cleanup-"`
|
||||
|
||||
EnableNewAuth bool `default:"false" help:"Experimental: enable new authentication."`
|
||||
|
||||
BatchSize int `default:"100" help:"Experimental: maximum insertion batch size."`
|
||||
MaxBsonObjectSizeMiB int `default:"16" help:"Experimental: maximum BSON object size in MiB."`
|
||||
Dev struct {
|
||||
RecordsDir string `hidden:""`
|
||||
|
||||
Telemetry struct {
|
||||
URL string `default:"https://beacon.ferretdb.com/" help:"Telemetry: reporting URL."`
|
||||
UndecidedDelay time.Duration `default:"1h" help:"Telemetry: delay for undecided state."`
|
||||
ReportInterval time.Duration `default:"24h" help:"Telemetry: report interval."`
|
||||
ReportTimeout time.Duration `default:"5s" help:"Telemetry: report timeout."`
|
||||
Package string `default:"" help:"Telemetry: custom package type."`
|
||||
URL string `default:"https://beacon.ferretdb.com/" hidden:""`
|
||||
UndecidedDelay time.Duration `default:"1h" hidden:""`
|
||||
ReportInterval time.Duration `default:"24h" hidden:""`
|
||||
Package string `default:"" hidden:""`
|
||||
} `embed:"" prefix:"telemetry-"`
|
||||
} `embed:"" prefix:"test-"`
|
||||
} `embed:"" prefix:"dev-"`
|
||||
}
|
||||
|
||||
// The postgreSQLFlags struct represents flags that are used by the "postgresql" backend.
|
||||
//
|
||||
// See main_postgresql.go.
|
||||
//
|
||||
//nolint:lll // some tags are long
|
||||
var postgreSQLFlags struct {
|
||||
PostgreSQLURL string `name:"postgresql-url" default:"postgres://127.0.0.1:5432/ferretdb" help:"PostgreSQL URL for 'postgresql' handler."`
|
||||
}
|
||||
|
||||
// The sqliteFlags struct represents flags that are used by the "sqlite" backend.
|
||||
//
|
||||
// See main_sqlite.go.
|
||||
var sqliteFlags struct {
|
||||
SQLiteURL string `name:"sqlite-url" default:"file:data/" help:"SQLite URI (directory) for 'sqlite' handler."`
|
||||
}
|
||||
|
||||
// The mySQLFlags struct represents flags that are used by the "mysql" backend.
|
||||
//
|
||||
// See main_mysql.go.
|
||||
var mySQLFlags struct {
|
||||
MySQLURL string `name:"mysql-url" default:"mysql://127.0.0.1:3306/ferretdb" help:"MySQL URL for 'mysql' handler" hidden:""`
|
||||
}
|
||||
|
||||
// The hanaFlags struct represents flags that are used by the "hana" handler.
|
||||
//
|
||||
// See main_hana.go.
|
||||
var hanaFlags struct {
|
||||
HANAURL string `name:"hana-url" help:"SAP HANA URL for 'hana' handler"`
|
||||
}
|
||||
|
||||
// handlerFlags is a map of handler names to their flags.
|
||||
var handlerFlags = map[string]any{}
|
||||
|
||||
// setCLIPlugins adds Kong flags for handlers in the right order.
|
||||
func setCLIPlugins() {
|
||||
handlers := registry.Handlers()
|
||||
|
||||
if len(handlers) != len(handlerFlags) {
|
||||
panic("handlers and handlerFlags are not in sync")
|
||||
}
|
||||
|
||||
for _, h := range handlers {
|
||||
f := handlerFlags[h]
|
||||
if f == nil {
|
||||
panic(fmt.Sprintf("handler %q has no flags", h))
|
||||
}
|
||||
|
||||
cli.Plugins = append(cli.Plugins, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional variables for the kong parsers.
|
||||
// Additional variables for [kong.Parse].
|
||||
var (
|
||||
logLevels = []string{
|
||||
slog.LevelDebug.String(),
|
||||
@@ -196,9 +126,6 @@ var (
|
||||
logFormats = []string{"console", "text", "json"}
|
||||
|
||||
kongOptions = []kong.Option{
|
||||
kong.HelpOptions{
|
||||
Compact: true,
|
||||
},
|
||||
kong.Vars{
|
||||
"default_log_level": defaultLogLevel().String(),
|
||||
"default_mode": clientconn.AllModes[0],
|
||||
@@ -206,22 +133,22 @@ var (
|
||||
"enum_log_format": strings.Join(logFormats, ","),
|
||||
"enum_mode": strings.Join(clientconn.AllModes, ","),
|
||||
|
||||
"help_handler": fmt.Sprintf("Backend handler: '%s'.", strings.Join(registry.Handlers(), "', '")),
|
||||
"help_log_format": fmt.Sprintf("Log format: '%s'.", strings.Join(logFormats, "', '")),
|
||||
"help_log_level": fmt.Sprintf("Log level: '%s'.", strings.Join(logLevels, "', '")),
|
||||
"help_mode": fmt.Sprintf("Operation mode: '%s'.", strings.Join(clientconn.AllModes, "', '")),
|
||||
"help_telemetry": "Enable or disable basic telemetry reporting. See https://beacon.ferretdb.com.",
|
||||
},
|
||||
kong.DefaultEnvars("FERRETDB"),
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
setCLIPlugins()
|
||||
ctx := kong.Parse(&cli, kongOptions...)
|
||||
|
||||
switch ctx.Command() {
|
||||
case "run":
|
||||
run()
|
||||
|
||||
case "ping":
|
||||
logger := setupLogger(cli.Log.Format, "")
|
||||
checkFlags(logger)
|
||||
@@ -244,7 +171,7 @@ func main() {
|
||||
|
||||
// defaultLogLevel returns the default log level.
|
||||
func defaultLogLevel() slog.Level {
|
||||
if version.Get().DebugBuild {
|
||||
if version.Get().DevBuild {
|
||||
return slog.LevelDebug
|
||||
}
|
||||
|
||||
@@ -253,13 +180,13 @@ func defaultLogLevel() slog.Level {
|
||||
|
||||
// setupState setups state provider.
|
||||
func setupState() *state.Provider {
|
||||
var f string
|
||||
if cli.StateDir == "" || cli.StateDir == "-" {
|
||||
log.Fatal("State directory must be set.")
|
||||
}
|
||||
|
||||
if dir := cli.StateDir; dir != "" && dir != "-" {
|
||||
var err error
|
||||
if f, err = filepath.Abs(filepath.Join(dir, "state.json")); err != nil {
|
||||
log.Fatalf("Failed to get path for state file: %s.", err)
|
||||
}
|
||||
f, err := filepath.Abs(filepath.Join(cli.StateDir, "state.json"))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get path for state file: %s.", err)
|
||||
}
|
||||
|
||||
sp, err := state.NewProvider(f)
|
||||
@@ -290,7 +217,7 @@ func setupMetrics(stateProvider *state.Provider) prometheus.Registerer {
|
||||
return r
|
||||
}
|
||||
|
||||
// setupLogger creates a logger with the level defined from cli.
|
||||
// setupLogger setups slog logger.
|
||||
func setupLogger(format string, uuid string) *slog.Logger {
|
||||
var level slog.Level
|
||||
if err := level.UnmarshalText([]byte(cli.Log.Level)); err != nil {
|
||||
@@ -302,28 +229,25 @@ func setupLogger(format string, uuid string) *slog.Logger {
|
||||
Level: level,
|
||||
}
|
||||
logging.Setup(opts, uuid)
|
||||
logger := slog.Default()
|
||||
|
||||
return slog.Default()
|
||||
return logger
|
||||
}
|
||||
|
||||
// checkFlags checks that CLI flags are not self-contradictory.
|
||||
func checkFlags(l *slog.Logger) {
|
||||
func checkFlags(logger *slog.Logger) {
|
||||
ctx := context.Background()
|
||||
|
||||
if cli.Setup.Database != "" && !cli.Test.EnableNewAuth {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "--setup-database requires --test-enable-new-auth")
|
||||
if devbuild.Enabled {
|
||||
logger.WarnContext(ctx, "This is a development build. The performance will be affected.")
|
||||
}
|
||||
|
||||
if (cli.Setup.Database == "") != (cli.Setup.Username == "") {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "--setup-database should be used together with --setup-username")
|
||||
if logger.Enabled(ctx, slog.LevelDebug) {
|
||||
logger.WarnContext(ctx, "Debug logging enabled. The performance will be affected.")
|
||||
}
|
||||
|
||||
if cli.Test.DisablePushdown && cli.Test.EnableNestedPushdown {
|
||||
l.LogAttrs(
|
||||
ctx,
|
||||
logging.LevelFatal,
|
||||
"--test-disable-pushdown and --test-enable-nested-pushdown should not be set at the same time",
|
||||
)
|
||||
if !cli.Auth {
|
||||
logger.WarnContext(ctx, "Authentication is disabled. The server will accept any connection.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +263,7 @@ func dumpMetrics() {
|
||||
// run sets up environment based on provided flags and runs FerretDB.
|
||||
func run() {
|
||||
// to increase a chance of resource finalizers to spot problems
|
||||
if debugbuild.Enabled {
|
||||
if devbuild.Enabled {
|
||||
defer func() {
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
@@ -348,17 +272,17 @@ func run() {
|
||||
|
||||
info := version.Get()
|
||||
|
||||
if p := cli.Test.Telemetry.Package; p != "" {
|
||||
if p := cli.Dev.Telemetry.Package; p != "" {
|
||||
info.Package = p
|
||||
}
|
||||
|
||||
if cli.Version {
|
||||
fmt.Fprintln(os.Stdout, "version:", info.Version)
|
||||
fmt.Fprintln(os.Stdout, "commit:", info.Commit)
|
||||
fmt.Fprintln(os.Stdout, "branch:", info.Branch)
|
||||
fmt.Fprintln(os.Stdout, "dirty:", info.Dirty)
|
||||
fmt.Fprintln(os.Stdout, "package:", info.Package)
|
||||
fmt.Fprintln(os.Stdout, "debugBuild:", info.DebugBuild)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "version:", info.Version)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "commit:", info.Commit)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "branch:", info.Branch)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "dirty:", info.Dirty)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "package:", info.Package)
|
||||
_, _ = fmt.Fprintln(os.Stdout, "devBuild:", info.DevBuild)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -376,7 +300,7 @@ func run() {
|
||||
slog.String("branch", info.Branch),
|
||||
slog.Bool("dirty", info.Dirty),
|
||||
slog.String("package", info.Package),
|
||||
slog.Bool("debugBuild", info.DebugBuild),
|
||||
slog.Bool("devBuild", info.DevBuild),
|
||||
slog.Any("buildEnvironment", info.BuildEnvironment),
|
||||
}
|
||||
logUUID := stateProvider.Get().UUID
|
||||
@@ -391,19 +315,18 @@ func run() {
|
||||
|
||||
logger.LogAttrs(context.Background(), slog.LevelInfo, "Starting FerretDB "+info.Version+"...", startupFields...)
|
||||
|
||||
if debugbuild.Enabled {
|
||||
logger.Info("This is a debug build. The performance will be affected.")
|
||||
}
|
||||
|
||||
if logger.Enabled(context.Background(), slog.LevelDebug) {
|
||||
logger.Info("Debug logging enabled. The security and performance will be affected.")
|
||||
}
|
||||
|
||||
checkFlags(logger)
|
||||
|
||||
if _, err := maxprocs.Set(maxprocs.Logger(func(format string, a ...any) {
|
||||
logger.Debug(fmt.Sprintf(format, a...))
|
||||
})); err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -411,7 +334,7 @@ func run() {
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
logger.Info("Stopping...")
|
||||
logger.InfoContext(ctx, "Stopping")
|
||||
|
||||
// second SIGTERM should immediately stop the process
|
||||
stop()
|
||||
@@ -444,6 +367,7 @@ func run() {
|
||||
|
||||
return listener.Load().Listening()
|
||||
},
|
||||
|
||||
Readyz: ready.Probe,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -478,70 +402,64 @@ func run() {
|
||||
|
||||
metrics := connmetrics.NewListenerMetrics()
|
||||
|
||||
wg.Add(1)
|
||||
{
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
l := logging.WithName(logger, "telemetry")
|
||||
opts := &telemetry.NewReporterOpts{
|
||||
URL: cli.Test.Telemetry.URL,
|
||||
F: &cli.Telemetry,
|
||||
DNT: os.Getenv("DO_NOT_TRACK"),
|
||||
ExecName: os.Args[0],
|
||||
P: stateProvider,
|
||||
ConnMetrics: metrics.ConnMetrics,
|
||||
L: l,
|
||||
UndecidedDelay: cli.Test.Telemetry.UndecidedDelay,
|
||||
ReportInterval: cli.Test.Telemetry.ReportInterval,
|
||||
ReportTimeout: cli.Test.Telemetry.ReportTimeout,
|
||||
}
|
||||
l := logging.WithName(logger, "telemetry")
|
||||
|
||||
r, err := telemetry.NewReporter(opts)
|
||||
if err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create telemetry reporter", logging.Error(err))
|
||||
}
|
||||
file, err := filepath.Abs(filepath.Join(cli.StateDir, "telemetry.json"))
|
||||
if err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "Failed to get path for local telemetry report file", logging.Error(err))
|
||||
}
|
||||
|
||||
r.Run(ctx)
|
||||
}()
|
||||
r, err := telemetry.NewReporter(&telemetry.NewReporterOpts{
|
||||
URL: cli.Dev.Telemetry.URL,
|
||||
File: file,
|
||||
F: &cli.Telemetry,
|
||||
DNT: os.Getenv("DO_NOT_TRACK"),
|
||||
ExecName: os.Args[0],
|
||||
P: stateProvider,
|
||||
ConnMetrics: metrics.ConnMetrics,
|
||||
L: l,
|
||||
UndecidedDelay: cli.Dev.Telemetry.UndecidedDelay,
|
||||
ReportInterval: cli.Dev.Telemetry.ReportInterval,
|
||||
})
|
||||
if err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "Failed to create telemetry reporter", logging.Error(err))
|
||||
}
|
||||
|
||||
h, closeBackend, err := registry.NewHandler(cli.Handler, ®istry.NewHandlerOpts{
|
||||
Logger: logger,
|
||||
ConnMetrics: metrics.ConnMetrics,
|
||||
StateProvider: stateProvider,
|
||||
TCPHost: cli.Listen.Addr,
|
||||
ReplSetName: cli.ReplSetName,
|
||||
|
||||
SetupDatabase: cli.Setup.Database,
|
||||
SetupUsername: cli.Setup.Username,
|
||||
SetupPassword: password.WrapPassword(cli.Setup.Password),
|
||||
SetupTimeout: cli.Setup.Timeout,
|
||||
|
||||
PostgreSQLURL: postgreSQLFlags.PostgreSQLURL,
|
||||
|
||||
SQLiteURL: sqliteFlags.SQLiteURL,
|
||||
|
||||
HANAURL: hanaFlags.HANAURL,
|
||||
|
||||
MySQLURL: mySQLFlags.MySQLURL,
|
||||
|
||||
TestOpts: registry.TestOpts{
|
||||
DisablePushdown: cli.Test.DisablePushdown,
|
||||
EnableNestedPushdown: cli.Test.EnableNestedPushdown,
|
||||
CappedCleanupInterval: cli.Test.CappedCleanup.Interval,
|
||||
CappedCleanupPercentage: cli.Test.CappedCleanup.Percentage,
|
||||
EnableNewAuth: cli.Test.EnableNewAuth,
|
||||
BatchSize: cli.Test.BatchSize,
|
||||
MaxBsonObjectSizeBytes: cli.Test.MaxBsonObjectSizeMiB * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogAttrs(ctx, logging.LevelFatal, "Failed to construct handler", logging.Error(err))
|
||||
r.Run(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
defer closeBackend()
|
||||
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))
|
||||
}
|
||||
|
||||
l, err := clientconn.Listen(&clientconn.NewListenerOpts{
|
||||
defer p.Close()
|
||||
|
||||
handlerOpts := &handler.NewOpts{
|
||||
Pool: p,
|
||||
Auth: cli.Auth,
|
||||
|
||||
TCPHost: cli.Listen.Addr,
|
||||
ReplSetName: cli.ReplSetName,
|
||||
|
||||
L: logging.WithName(logger, "handler"),
|
||||
ConnMetrics: metrics.ConnMetrics,
|
||||
StateProvider: stateProvider,
|
||||
}
|
||||
|
||||
h, err := handler.New(handlerOpts)
|
||||
if err != nil {
|
||||
handlerOpts.L.LogAttrs(ctx, logging.LevelFatal, "Failed to construct handler", logging.Error(err))
|
||||
}
|
||||
|
||||
lis, err := clientconn.Listen(&clientconn.NewListenerOpts{
|
||||
TCP: cli.Listen.Addr,
|
||||
Unix: cli.Listen.Unix,
|
||||
|
||||
@@ -559,23 +477,44 @@ func run() {
|
||||
Metrics: metrics,
|
||||
Handler: h,
|
||||
Logger: logger,
|
||||
TestRecordsDir: cli.Test.RecordsDir,
|
||||
TestRecordsDir: cli.Dev.RecordsDir,
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogAttrs(ctx, logging.LevelFatal, "Failed to construct listener", logging.Error(err))
|
||||
}
|
||||
|
||||
listener.Store(l)
|
||||
if addr := cli.Listen.DataAPIAddr; addr != "" && addr != "-" {
|
||||
wg.Add(1)
|
||||
|
||||
metricsRegisterer.MustRegister(l)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
l.Run(ctx)
|
||||
l := logging.WithName(logger, "dataapi")
|
||||
|
||||
logger.Info("Listener stopped")
|
||||
var lis *dataapi.Listener
|
||||
|
||||
lis, err = dataapi.Listen(&dataapi.ListenOpts{
|
||||
TCPAddr: addr,
|
||||
L: l,
|
||||
Handler: h,
|
||||
})
|
||||
if err != nil {
|
||||
l.LogAttrs(ctx, logging.LevelFatal, "Failed to construct DataAPI listener", logging.Error(err))
|
||||
}
|
||||
|
||||
lis.Run(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
listener.Store(lis)
|
||||
|
||||
metricsRegisterer.MustRegister(lis)
|
||||
|
||||
lis.Run(ctx)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if info.DebugBuild {
|
||||
if info.DevBuild {
|
||||
dumpMetrics()
|
||||
}
|
||||
}
|
||||
@@ -1,22 +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.
|
||||
|
||||
//go:build ferretdb_hana
|
||||
|
||||
package main
|
||||
|
||||
// init adds "hana" handler flags when "ferretdb_hana" build tag is provided.
|
||||
func init() {
|
||||
handlerFlags["hana"] = &hanaFlags
|
||||
}
|
||||
@@ -1,22 +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.
|
||||
|
||||
//go:build !ferretdb_no_sqlite
|
||||
|
||||
package main
|
||||
|
||||
// init adds "sqlite" backend flags.
|
||||
func init() {
|
||||
handlerFlags["sqlite"] = &sqliteFlags
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
@@ -38,7 +39,7 @@ func TestVersion(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(testutil.Ctx(t), 5*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
bin := filepath.Join(testutil.BinDir, "ferretdb")
|
||||
bin := filepath.Join("..", "..", "bin", "ferretdb")
|
||||
|
||||
cmd := exec.CommandContext(ctx, bin, "--version")
|
||||
b, err := cmd.Output()
|
||||
@@ -57,3 +58,16 @@ func TestVersion(t *testing.T) {
|
||||
|
||||
assert.Equal(t, commit[1], revision[1])
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var res struct {
|
||||
Deps []string `json:"Deps"`
|
||||
}
|
||||
b, err := exec.Command("go", "list", "-json").Output()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(b, &res))
|
||||
|
||||
assert.NotContains(t, res.Deps, "testing", `package "testing" should not be imported by non-testing code`)
|
||||
}
|
||||
@@ -21,7 +21,8 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// stateFileProblem adds details to the state file access error.
|
||||
@@ -41,7 +42,7 @@ func stateFileProblem(f string, err error) string {
|
||||
var username, group string
|
||||
var uid, gid uint64
|
||||
|
||||
if s, _ := fi.Sys().(*syscall.Stat_t); s != nil {
|
||||
if s, _ := fi.Sys().(*unix.Stat_t); s != nil {
|
||||
uid = uint64(s.Uid)
|
||||
if u, _ := user.LookupId(strconv.FormatUint(uid, 10)); u != nil {
|
||||
username = u.Username
|
||||
|
||||
@@ -1,127 +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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/logging"
|
||||
)
|
||||
|
||||
// ReadyZ represents the Readiness probe, which is used to run `ping`
|
||||
// command against the FerretDB instance specified by cli flags.
|
||||
type ReadyZ struct {
|
||||
l *slog.Logger
|
||||
}
|
||||
|
||||
// Probe executes ping queries to open listeners, and returns true if they succeed.
|
||||
// Any errors that occure are passed through ReadyZ.l listener.
|
||||
//
|
||||
// It is only executed if --setup-database flag is set.
|
||||
func (ready *ReadyZ) Probe(ctx context.Context) bool {
|
||||
l := ready.l
|
||||
|
||||
if cli.Setup.Database == "" {
|
||||
l.InfoContext(ctx, "Setup database not specified - skipping ping")
|
||||
return true
|
||||
}
|
||||
|
||||
var urls []string
|
||||
|
||||
if cli.Listen.Addr != "" {
|
||||
host, port, err := net.SplitHostPort(cli.Listen.Addr)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "Getting host and port failed", logging.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
l.DebugContext(ctx, fmt.Sprintf("--listen-addr flag is set. Ping to %s will be performed", cli.Listen.Addr))
|
||||
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
|
||||
l.DebugContext(ctx, fmt.Sprintf("Host not specified, defaulting to %s", host))
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: net.JoinHostPort(host, port),
|
||||
Path: cli.Setup.Database,
|
||||
User: url.UserPassword(cli.Setup.Username, cli.Setup.Password),
|
||||
}
|
||||
|
||||
urls = append(urls, u.String())
|
||||
}
|
||||
|
||||
if cli.Listen.TLS != "" {
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4427
|
||||
l.WarnContext(ctx, "TLS ping is not implemented yet")
|
||||
}
|
||||
|
||||
if cli.Listen.Unix != "" {
|
||||
l.DebugContext(ctx, fmt.Sprintf("--listen-unix flag is set. Ping to %s will be performed", cli.Listen.Unix))
|
||||
|
||||
urls = append(urls, "mongodb://"+url.PathEscape(cli.Listen.Unix))
|
||||
}
|
||||
|
||||
if len(urls) == 0 {
|
||||
l.InfoContext(ctx, "Neither --listen-addr nor --listen-unix nor --listen-tls flags were specified - skipping ping")
|
||||
return true
|
||||
}
|
||||
|
||||
for _, u := range urls {
|
||||
l.DebugContext(ctx, fmt.Sprintf("Pinging %s", u))
|
||||
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, cli.Setup.Timeout)
|
||||
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(u))
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "Connection failed", logging.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
pingErr := client.Ping(ctx, nil)
|
||||
|
||||
// do not leave connection open when ping error causes os.Exit with Fatal
|
||||
if err = client.Disconnect(ctx); err != nil {
|
||||
l.ErrorContext(ctx, "Disconnect failed", logging.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
if pingErr != nil {
|
||||
l.ErrorContext(ctx, "Ping failed", logging.Error(pingErr))
|
||||
return false
|
||||
}
|
||||
|
||||
var uri *url.URL
|
||||
if uri, err = url.Parse(u); err == nil {
|
||||
u = uri.Redacted()
|
||||
}
|
||||
|
||||
l.InfoContext(ctx, fmt.Sprintf("Ping to %s successful", u))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/logging"
|
||||
)
|
||||
|
||||
// ReadyZ represents the Readiness probe.
|
||||
//
|
||||
// It connects to FerretDB with Go driver and sends `ping` command.
|
||||
type ReadyZ struct {
|
||||
l *slog.Logger
|
||||
}
|
||||
|
||||
// Probe implements [debug.Probe].
|
||||
func (r *ReadyZ) Probe(ctx context.Context) bool {
|
||||
var urls []string
|
||||
|
||||
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))
|
||||
return false
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: net.JoinHostPort(host, port),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
urls = append(urls, u.String())
|
||||
}
|
||||
|
||||
if cli.Listen.TLS != "" {
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/4427
|
||||
r.l.WarnContext(ctx, "TLS ping is not implemented yet")
|
||||
}
|
||||
|
||||
if cli.Listen.Unix != "" {
|
||||
urls = append(urls, "mongodb://"+url.PathEscape(cli.Listen.Unix))
|
||||
}
|
||||
|
||||
if len(urls) == 0 {
|
||||
r.l.InfoContext(ctx, "Nothing to ping")
|
||||
return true
|
||||
}
|
||||
|
||||
for _, u := range urls {
|
||||
r.l.DebugContext(ctx, fmt.Sprintf("Pinging %s", u))
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(u))
|
||||
if err != nil {
|
||||
r.l.ErrorContext(ctx, "Connection failed", logging.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
pingErr := client.Ping(ctx, nil)
|
||||
|
||||
err = client.Disconnect(ctx)
|
||||
|
||||
if pingErr != nil {
|
||||
r.l.ErrorContext(ctx, "Ping failed", logging.Error(pingErr))
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.l.ErrorContext(ctx, "Disconnect failed", logging.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
r.l.InfoContext(ctx, fmt.Sprintf("Ping to %s successful", u))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+33
-66
@@ -5,8 +5,9 @@ services:
|
||||
postgres:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: ${POSTGRES_DOCKERFILE:-postgres}.Dockerfile
|
||||
dockerfile: postgres-documentdb.Dockerfile
|
||||
container_name: ferretdb_postgres
|
||||
platform: linux/amd64
|
||||
command: >
|
||||
postgres
|
||||
-c log_min_duration_statement=1000ms
|
||||
@@ -22,88 +23,54 @@ services:
|
||||
- TZ=America/St_Johns
|
||||
- POSTGRES_USER=username
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
- POSTGRES_DB=ferretdb
|
||||
|
||||
postgres_secured:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: postgres.Dockerfile
|
||||
container_name: ferretdb_postgres_secured
|
||||
command: >
|
||||
postgres
|
||||
-c log_min_duration_statement=1000ms
|
||||
-c log_min_error_statement=WARNING
|
||||
-c log_min_messages=WARNING
|
||||
-c max_connections=400
|
||||
ports:
|
||||
- 5433:5432
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# UTC−03:30/−02:30. Set to catch timezone problems.
|
||||
- TZ=America/St_Johns
|
||||
- POSTGRES_USER=username
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_DB=ferretdb
|
||||
|
||||
mysql:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: mysql.Dockerfile
|
||||
container_name: ferretdb_mysql
|
||||
command:
|
||||
- --slow_query_log=1
|
||||
- --long_query_time=1
|
||||
- --log_error_verbosity=2
|
||||
- --max_connections=200
|
||||
ports:
|
||||
- 3306:3306
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# UTC−03:30/−02:30. Set to catch timezone problems.
|
||||
- TZ=America/St_Johns
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
- MYSQL_USER=username
|
||||
- MYSQL_PASSWORD=password
|
||||
- MYSQL_DATABASE=ferretdb
|
||||
- POSTGRES_DB=postgres
|
||||
volumes:
|
||||
- ./build/testscripts:/testscripts
|
||||
|
||||
mongodb:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: mongo.Dockerfile
|
||||
dockerfile: mongodb.Dockerfile
|
||||
container_name: ferretdb_mongodb
|
||||
command: --config /etc/mongodb.conf
|
||||
ports:
|
||||
- 37017:37017
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# Always UTC+05:45. Set to catch timezone problems.
|
||||
- TZ=Asia/Kathmandu
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 40000
|
||||
hard: 80000
|
||||
volumes:
|
||||
- ./build/mongodb.conf:/etc/mongodb.conf
|
||||
|
||||
mongodb-secure:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: mongodb.Dockerfile
|
||||
container_name: ferretdb_mongodb_secure
|
||||
command: --config /etc/mongodb.conf
|
||||
ports:
|
||||
- 47017:47017
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# Always UTC+05:45. Set to catch timezone problems.
|
||||
- TZ=Asia/Kathmandu
|
||||
volumes:
|
||||
- ./build/certs:/etc/certs
|
||||
- ./build/mongodb.conf:/etc/mongodb.conf
|
||||
|
||||
mongodb_secured:
|
||||
build:
|
||||
context: ./build/deps
|
||||
dockerfile: mongo.Dockerfile
|
||||
container_name: ferretdb_mongodb_secured
|
||||
command: --config /etc/mongodb.conf
|
||||
ports:
|
||||
- 47018:47018
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# Always UTC+05:45. Set to catch timezone problems.
|
||||
- TZ=Asia/Kathmandu
|
||||
- MONGO_INITDB_ROOT_USERNAME=username
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 40000
|
||||
hard: 80000
|
||||
volumes:
|
||||
- ./build/certs:/etc/certs
|
||||
- ./build/mongodb_secured.conf:/etc/mongodb.conf
|
||||
- ./build/mongodb-secure.conf:/etc/mongodb.conf
|
||||
|
||||
# for test scripts
|
||||
legacy-mongo-shell:
|
||||
|
||||
@@ -1,219 +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 ferretdb provides embeddable FerretDB implementation.
|
||||
//
|
||||
// See [github.com/FerretDB/FerretDB/build/version] package documentation
|
||||
// for information about Go build tags that affect this package.
|
||||
package ferretdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
|
||||
"github.com/FerretDB/FerretDB/build/version"
|
||||
"github.com/FerretDB/FerretDB/internal/clientconn"
|
||||
"github.com/FerretDB/FerretDB/internal/clientconn/connmetrics"
|
||||
"github.com/FerretDB/FerretDB/internal/handler/registry"
|
||||
"github.com/FerretDB/FerretDB/internal/util/state"
|
||||
)
|
||||
|
||||
// Config represents FerretDB configuration.
|
||||
type Config struct {
|
||||
Listener ListenerConfig
|
||||
|
||||
// Logger to use; if nil, `slog.Default()` is used.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Handler to use; one of `postgresql` or `sqlite`.
|
||||
Handler string
|
||||
|
||||
// PostgreSQL connection string for `postgresql` handler.
|
||||
// See:
|
||||
// - https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool#ParseConfig
|
||||
// - https://pkg.go.dev/github.com/jackc/pgx/v5#ParseConfig
|
||||
// - https://pkg.go.dev/github.com/jackc/pgx/v5/pgconn#ParseConfig
|
||||
PostgreSQLURL string // For example: `postgres://hostname:5432/ferretdb`.
|
||||
|
||||
// SQLite URI (directory) for `sqlite` handler.
|
||||
// See https://www.sqlite.org/uri.html.
|
||||
SQLiteURL string // For example: `file:data/`.
|
||||
}
|
||||
|
||||
// ListenerConfig represents listener configuration.
|
||||
type ListenerConfig struct {
|
||||
// Listen TCP address.
|
||||
// If empty, TCP listener is disabled.
|
||||
TCP string
|
||||
|
||||
// Listen Unix domain socket path.
|
||||
// If empty, Unix listener is disabled.
|
||||
Unix string
|
||||
|
||||
// Listen TLS address.
|
||||
// If empty, TLS listener is disabled.
|
||||
TLS string
|
||||
|
||||
// Server certificate path.
|
||||
TLSCertFile string
|
||||
|
||||
// Server key path.
|
||||
TLSKeyFile string
|
||||
|
||||
// Root CA certificate path.
|
||||
TLSCAFile string
|
||||
}
|
||||
|
||||
// FerretDB represents an instance of embeddable FerretDB implementation.
|
||||
type FerretDB struct {
|
||||
config *Config
|
||||
|
||||
closeBackend func()
|
||||
|
||||
l *clientconn.Listener
|
||||
}
|
||||
|
||||
// New creates a new instance of embeddable FerretDB implementation.
|
||||
func New(config *Config) (*FerretDB, error) {
|
||||
version.Get().Package = "embedded"
|
||||
|
||||
if config.Listener.TCP == "" &&
|
||||
config.Listener.Unix == "" &&
|
||||
config.Listener.TLS == "" {
|
||||
return nil, errors.New("Listener TCP, Unix and TLS are empty")
|
||||
}
|
||||
|
||||
sp, err := state.NewProvider("")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct handler: %s", err)
|
||||
}
|
||||
|
||||
// Telemetry reporter is not created or running anyway,
|
||||
// but disable telemetry explicitly to disable confusing startupWarnings.
|
||||
err = sp.Update(func(s *state.State) {
|
||||
s.DisableTelemetry()
|
||||
s.TelemetryLocked = true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct handler: %s", err)
|
||||
}
|
||||
|
||||
metrics := connmetrics.NewListenerMetrics()
|
||||
|
||||
logger := config.Logger
|
||||
if logger == nil {
|
||||
logger = slog.Default()
|
||||
}
|
||||
|
||||
h, closeBackend, err := registry.NewHandler(config.Handler, ®istry.NewHandlerOpts{
|
||||
Logger: logger,
|
||||
ConnMetrics: metrics.ConnMetrics,
|
||||
StateProvider: sp,
|
||||
TCPHost: config.Listener.TCP,
|
||||
|
||||
PostgreSQLURL: config.PostgreSQLURL,
|
||||
|
||||
SQLiteURL: config.SQLiteURL,
|
||||
|
||||
TestOpts: registry.TestOpts{
|
||||
CappedCleanupPercentage: 10,
|
||||
BatchSize: 100,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if closeBackend != nil {
|
||||
closeBackend()
|
||||
}
|
||||
return nil, fmt.Errorf("failed to construct handler: %s", err)
|
||||
}
|
||||
|
||||
l, err := clientconn.Listen(&clientconn.NewListenerOpts{
|
||||
TCP: config.Listener.TCP,
|
||||
Unix: config.Listener.Unix,
|
||||
|
||||
TLS: config.Listener.TLS,
|
||||
TLSCertFile: config.Listener.TLSCertFile,
|
||||
TLSKeyFile: config.Listener.TLSKeyFile,
|
||||
TLSCAFile: config.Listener.TLSCAFile,
|
||||
|
||||
Mode: clientconn.NormalMode,
|
||||
Metrics: metrics,
|
||||
Handler: h,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct handler: %s", err)
|
||||
}
|
||||
|
||||
return &FerretDB{
|
||||
config: config,
|
||||
closeBackend: closeBackend,
|
||||
l: l,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run runs FerretDB until ctx is canceled.
|
||||
//
|
||||
// When this method returns, listener and all connections, as well as handler are closed.
|
||||
//
|
||||
// It is required to run this method in order to initialize the listeners with their respective
|
||||
// IP address and port. Calling methods which require the listener's address (eg: [*FerretDB.MongoDBURI]
|
||||
// requires it for configuring its Host URL) before calling this method might result in a deadlock.
|
||||
func (f *FerretDB) Run(ctx context.Context) error {
|
||||
defer f.closeBackend()
|
||||
|
||||
f.l.Run(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MongoDBURI returns MongoDB URI for this FerretDB instance.
|
||||
//
|
||||
// TCP's connection string is returned if both TCP and Unix listeners are enabled.
|
||||
// TLS is preferred over both.
|
||||
func (f *FerretDB) MongoDBURI() string {
|
||||
var u *url.URL
|
||||
|
||||
switch {
|
||||
case f.config.Listener.TLS != "":
|
||||
q := url.Values{
|
||||
"tls": []string{"true"},
|
||||
}
|
||||
|
||||
u = &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: f.l.TLSAddr().String(),
|
||||
Path: "/",
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
case f.config.Listener.TCP != "":
|
||||
u = &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: f.l.TCPAddr().String(),
|
||||
Path: "/",
|
||||
}
|
||||
case f.config.Listener.Unix != "":
|
||||
// MongoDB really wants Unix domain socket path in the host part of the URI
|
||||
u = &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: f.l.UnixAddr().String(),
|
||||
Path: "/",
|
||||
}
|
||||
}
|
||||
|
||||
return u.String()
|
||||
}
|
||||
@@ -1,200 +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 ferretdb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/FerretDB/FerretDB/ferretdb"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
)
|
||||
|
||||
func Example_tcp() {
|
||||
f, err := ferretdb.New(&ferretdb.Config{
|
||||
Listener: ferretdb.ListenerConfig{
|
||||
TCP: "127.0.0.1:17027",
|
||||
},
|
||||
Logger: slog.With("component", "ferretdb"),
|
||||
Handler: "postgresql",
|
||||
PostgreSQLURL: "postgres://127.0.0.1:5432/ferretdb",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error)
|
||||
|
||||
go func() {
|
||||
done <- f.Run(ctx)
|
||||
}()
|
||||
|
||||
uri := f.MongoDBURI()
|
||||
fmt.Println(uri)
|
||||
|
||||
// Use MongoDB URI as usual.
|
||||
// For example:
|
||||
//
|
||||
// import "go.mongodb.org/mongo-driver/mongo"
|
||||
// import "go.mongodb.org/mongo-driver/mongo/options"
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
||||
|
||||
cancel()
|
||||
|
||||
err = <-done
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output: mongodb://127.0.0.1:17027/
|
||||
}
|
||||
|
||||
func Example_unix() {
|
||||
f, err := ferretdb.New(&ferretdb.Config{
|
||||
Listener: ferretdb.ListenerConfig{
|
||||
Unix: "/tmp/ferretdb.sock",
|
||||
},
|
||||
Logger: slog.With("component", "ferretdb"),
|
||||
Handler: "postgresql",
|
||||
PostgreSQLURL: "postgres://127.0.0.1:5432/ferretdb",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error)
|
||||
|
||||
go func() {
|
||||
done <- f.Run(ctx)
|
||||
}()
|
||||
|
||||
uri := f.MongoDBURI()
|
||||
fmt.Println(uri)
|
||||
|
||||
// Use MongoDB URI as usual.
|
||||
// For example:
|
||||
//
|
||||
// import "go.mongodb.org/mongo-driver/mongo"
|
||||
// import "go.mongodb.org/mongo-driver/mongo/options"
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
||||
|
||||
cancel()
|
||||
|
||||
err = <-done
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output: mongodb://%2Ftmp%2Fferretdb.sock/
|
||||
}
|
||||
|
||||
func Example_tls() {
|
||||
f, err := ferretdb.New(&ferretdb.Config{
|
||||
Listener: ferretdb.ListenerConfig{
|
||||
TLS: "127.0.0.1:17028",
|
||||
TLSCertFile: filepath.Join(testutil.BuildCertsDir, "server-cert.pem"),
|
||||
TLSKeyFile: filepath.Join(testutil.BuildCertsDir, "server-key.pem"),
|
||||
TLSCAFile: filepath.Join(testutil.BuildCertsDir, "rootCA-cert.pem"),
|
||||
},
|
||||
Logger: slog.With("component", "ferretdb"),
|
||||
Handler: "postgresql",
|
||||
PostgreSQLURL: "postgres://127.0.0.1:5432/ferretdb",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error)
|
||||
|
||||
go func() {
|
||||
done <- f.Run(ctx)
|
||||
}()
|
||||
|
||||
uri := f.MongoDBURI()
|
||||
fmt.Println(uri)
|
||||
|
||||
// Use MongoDB URI as usual.
|
||||
// To connect to TLS listener, set TLS config.
|
||||
// For example:
|
||||
//
|
||||
// import "go.mongodb.org/mongo-driver/mongo"
|
||||
// import "go.mongodb.org/mongo-driver/mongo/options"
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
||||
|
||||
cancel()
|
||||
|
||||
err = <-done
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output: mongodb://127.0.0.1:17028/?tls=true
|
||||
}
|
||||
|
||||
func TestEmbedded(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(testutil.Ctx(t))
|
||||
|
||||
f, err := ferretdb.New(&ferretdb.Config{
|
||||
Listener: ferretdb.ListenerConfig{
|
||||
TCP: "127.0.0.1:0",
|
||||
},
|
||||
Logger: testutil.Logger(t),
|
||||
Handler: "postgresql",
|
||||
PostgreSQLURL: testutil.TestPostgreSQLURI(t, ctx, ""),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
require.NoError(t, f.Run(ctx))
|
||||
close(done)
|
||||
}()
|
||||
|
||||
uri := f.MongoDBURI()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
||||
require.NoError(t, err)
|
||||
|
||||
dbName, collName := testutil.DatabaseName(t), testutil.CollectionName(t)
|
||||
|
||||
//nolint:forbidigo // bson is required to use the driver
|
||||
_, err = client.Database(dbName).Collection(collName).InsertOne(ctx, bson.M{"foo": "bar"})
|
||||
require.NoError(t, err)
|
||||
|
||||
cancel()
|
||||
<-done
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/FerretDB/FerretDB
|
||||
module github.com/FerretDB/FerretDB/v2
|
||||
|
||||
go 1.23
|
||||
|
||||
@@ -6,75 +6,64 @@ toolchain go1.23.5
|
||||
|
||||
require (
|
||||
github.com/AlekSi/pointer v1.2.0
|
||||
github.com/FerretDB/wire v0.0.8
|
||||
github.com/SAP/go-hdb v1.12.0
|
||||
github.com/alecthomas/kong v0.9.0
|
||||
github.com/FerretDB/wire v0.0.16
|
||||
github.com/alecthomas/kong v1.6.1
|
||||
github.com/arl/statsviz v0.6.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
|
||||
github.com/jackc/pgx/v5 v5.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/prometheus/common v0.57.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/prometheus/common v0.62.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/xdg-go/scram v1.1.2
|
||||
github.com/xdg-go/stringprep v1.0.4
|
||||
go.mongodb.org/mongo-driver v1.16.1
|
||||
go.opentelemetry.io/otel v1.29.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
|
||||
go.opentelemetry.io/otel/sdk v1.29.0
|
||||
go.opentelemetry.io/otel/trace v1.29.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250106144430-8929309228b4
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||
go.mongodb.org/mongo-driver v1.17.2
|
||||
go.opentelemetry.io/otel v1.34.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0
|
||||
go.opentelemetry.io/otel/sdk v1.34.0
|
||||
go.opentelemetry.io/otel/trace v1.34.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250118192723-a8ea4be81f07
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
|
||||
golang.org/x/sys v0.29.0
|
||||
modernc.org/sqlite v1.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // 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.17.9 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.69.4 // indirect
|
||||
google.golang.org/protobuf v1.36.3 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
@@ -1,21 +1,21 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
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.0.8 h1:5kttr1Hd60vWbvllemMcxwqTx7yedVVxxOqsliFdMGw=
|
||||
github.com/FerretDB/wire v0.0.8/go.mod h1:6y7usTYfOlJc3w3l2R/PcViJjKSqyYQhrKa3aeAoekI=
|
||||
github.com/SAP/go-hdb v1.12.0 h1:ZSjQqVfOITKMiLN7ycbfRncrNcjzRw9/p349v4oEODI=
|
||||
github.com/SAP/go-hdb v1.12.0/go.mod h1:0DG4r/GryOydt1Z+ZardSS806ADjty2bR0wCPFpOzZA=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
|
||||
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
|
||||
github.com/FerretDB/wire v0.0.16 h1:87gEvXigjuV+4mYXzinqKLGzS1sRlJ8ChCaxxTpzt18=
|
||||
github.com/FerretDB/wire v0.0.16/go.mod h1:fw7JPAF0YHB/N7nE5yyRrBG1iujIeOYRYv89gFKu6e0=
|
||||
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.6.1 h1:/7bVimARU3uxPD0hbryPE8qWrS3Oz3kPQoxA/H2NKG8=
|
||||
github.com/alecthomas/kong v1.6.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/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/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -23,41 +23,36 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
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.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||
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/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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
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.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -66,73 +61,74 @@ 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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
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/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.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
|
||||
github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/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/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
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.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
|
||||
go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
|
||||
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
|
||||
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.0.0 h1:Jfd7XpdZa9yk3eY774bO7SWVb30noLSirL9nKTpavhI=
|
||||
go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA=
|
||||
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.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250106144430-8929309228b4 h1:2UmVXDw2sT/v4mRp1tucroUkHyqAbK1SXfh+ss3sKsE=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250106144430-8929309228b4/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250118192723-a8ea4be81f07 h1:Tuk3hxOkRoX4Xwph6/tRU1wGumEsVYM2TZfvAC6MllM=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250118192723-a8ea4be81f07/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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=
|
||||
@@ -147,7 +143,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -161,46 +156,20 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
@@ -5,6 +5,7 @@
|
||||
run:
|
||||
timeout: 3m
|
||||
|
||||
# ../bin/golangci-lint linters -c .golangci-new.yml
|
||||
linters-settings:
|
||||
asasalint:
|
||||
exclude:
|
||||
@@ -18,8 +19,6 @@ linters-settings:
|
||||
check-blank: false
|
||||
disable-default-exclusions: false
|
||||
exclude-functions:
|
||||
- (*database/sql.Rows).Close
|
||||
- (*github.com/FerretDB/FerretDB/internal/util/fsql.Rows).Close
|
||||
- (*go.mongodb.org/mongo-driver/mongo.Cursor).Close
|
||||
errorlint:
|
||||
# see caveats at https://github.com/polyfloyd/go-errorlint#fmterrorf-wrapping-verb
|
||||
@@ -78,6 +77,8 @@ linters:
|
||||
- misspell
|
||||
- nolintlint
|
||||
- sloglint
|
||||
- tagalign
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
@@ -116,6 +117,7 @@ linters:
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- grouper
|
||||
- iface
|
||||
- inamedparam
|
||||
- interfacebloat
|
||||
- intrange
|
||||
@@ -140,6 +142,7 @@ linters:
|
||||
- promlinter
|
||||
- protogetter
|
||||
- reassign
|
||||
- recvcheck
|
||||
- rowserrcheck
|
||||
- sloglint
|
||||
- spancheck
|
||||
@@ -153,7 +156,6 @@ linters:
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- usestdlibvars
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
|
||||
+35
-11
@@ -16,21 +16,21 @@ linters-settings:
|
||||
desc: use `context` package instead
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: use `slices` package instead
|
||||
- pkg: maps
|
||||
desc: use `golang.org/x/exp/maps` package instead
|
||||
# - pkg: golang.org/x/exp/maps
|
||||
# desc: use `maps` package instead
|
||||
commonerrors:
|
||||
files:
|
||||
- $all
|
||||
deny:
|
||||
- pkg: github.com/FerretDB/FerretDB/internal/handler/commonerrors
|
||||
- pkg: github.com/FerretDB/FerretDB/v2/internal/handler/commonerrors
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/FerretDB/FerretDB)
|
||||
- prefix(github.com/FerretDB/FerretDB/integration)
|
||||
- prefix(github.com/FerretDB/FerretDB/v2)
|
||||
- prefix(github.com/FerretDB/FerretDB/v2/integration)
|
||||
goconst:
|
||||
min-occurrences: 5
|
||||
ignore-tests: true
|
||||
@@ -44,6 +44,8 @@ linters-settings:
|
||||
- BUG
|
||||
- FIXME
|
||||
- HACK
|
||||
- lala
|
||||
- lalala
|
||||
goheader:
|
||||
# Please do not update it, including bumping a year.
|
||||
template: |-
|
||||
@@ -61,10 +63,12 @@ linters-settings:
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
goimports:
|
||||
local-prefixes: github.com/FerretDB/FerretDB
|
||||
local-prefixes: github.com/FerretDB/FerretDB/v2
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules: []
|
||||
modules:
|
||||
- github.com/FerretDB/FerretDB:
|
||||
reason: "Use `github.com/FerretDB/FerretDB/v2/...` instead."
|
||||
gosimple:
|
||||
checks: ["all"]
|
||||
govet:
|
||||
@@ -74,13 +78,16 @@ linters-settings:
|
||||
- nilness
|
||||
- shadow
|
||||
importas:
|
||||
no-unaliased: false
|
||||
no-extra-aliases: true
|
||||
# https://github.com/julz/importas/issues/15
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
- pkg: github.com/prometheus/client_model/go
|
||||
alias: dto
|
||||
- pkg: github.com/jaswdr/faker
|
||||
alias: fakerlib
|
||||
- pkg: github.com/xdg-go/scram
|
||||
alias: xdgscram
|
||||
- pkg: go.opentelemetry.io/otel/attribute
|
||||
alias: otelattribute
|
||||
- pkg: go.opentelemetry.io/otel/codes
|
||||
@@ -120,7 +127,7 @@ linters-settings:
|
||||
attr-only: true
|
||||
no-global: "all"
|
||||
context: "scope"
|
||||
static-msg: true
|
||||
static-msg: false # TODO https://github.com/FerretDB/FerretDB/issues/3421
|
||||
no-raw-keys: false # TODO https://github.com/FerretDB/FerretDB/issues/3421
|
||||
key-naming-case: snake
|
||||
args-on-sep-lines: false
|
||||
@@ -128,7 +135,23 @@ linters-settings:
|
||||
checks:
|
||||
- all
|
||||
- -SA1019 # ignore deprecation errors in existing code; new code is checked by the other configuration
|
||||
# unused
|
||||
tagalign:
|
||||
sort: true
|
||||
order:
|
||||
- name
|
||||
- cmd
|
||||
- arg
|
||||
- default
|
||||
- help
|
||||
unparam:
|
||||
check-exported: false
|
||||
unused:
|
||||
field-writes-are-uses: false
|
||||
post-statements-are-reads: false
|
||||
exported-fields-are-used: false
|
||||
parameters-are-used: true # it should be false, TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/823
|
||||
local-variables-are-used: false
|
||||
generated-is-used: false
|
||||
whitespace:
|
||||
multi-if: false
|
||||
multi-func: false
|
||||
@@ -157,6 +180,7 @@ linters:
|
||||
- revive
|
||||
- sloglint
|
||||
- staticcheck
|
||||
- tagalign
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ tasks:
|
||||
- >
|
||||
go test -count=1 {{.RACE_FLAG}} -run=TestEnvData
|
||||
-tags=ferretdb_testenvdata .
|
||||
-target-backend=ferretdb-postgresql
|
||||
-target-url='mongodb://127.0.0.1:27017/'
|
||||
-target-backend=mongodb
|
||||
-target-url='mongodb://username:password@127.0.0.1:47017/'
|
||||
- >
|
||||
go test -count=1 {{.RACE_FLAG}} -run=TestEnvData
|
||||
-tags=ferretdb_testenvdata .
|
||||
-target-backend=mongodb
|
||||
-target-url='mongodb://127.0.0.1:47017/'
|
||||
-target-backend=ferretdb
|
||||
-target-url='mongodb://username:password@127.0.0.1:27017/'
|
||||
|
||||
integration-gen:
|
||||
cmds:
|
||||
@@ -34,7 +34,6 @@ tasks:
|
||||
- ../bin/golangci-lint{{exeExt}} run --config=.golangci.yml
|
||||
- ../bin/golangci-lint{{exeExt}} run --config=.golangci-new.yml
|
||||
- ../bin/go-consistent{{exeExt}} -pedantic ./...
|
||||
|
||||
- go vet -vettool=../bin/checkswitch{{exeExt}} ./...
|
||||
- go vet -vettool=../bin/checkcomments{{exeExt}} ./...
|
||||
|
||||
@@ -50,79 +49,11 @@ tasks:
|
||||
-count={{.BENCH_COUNT}} -bench={{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-postgresql
|
||||
-postgresql-url=postgres://username@127.0.0.1:5432/ferretdb
|
||||
-target-backend=ferretdb
|
||||
-postgresql-url=postgres://username:password@127.0.0.1:5432/postgres
|
||||
| tee new-postgresql.txt
|
||||
- ../bin/benchstat{{exeExt}} old-postgresql.txt new-postgresql.txt
|
||||
|
||||
bench-postgresql-no-pushdown:
|
||||
desc: "Run benchmarks for `postgresql` backend without filter pushdown"
|
||||
cmds:
|
||||
- >
|
||||
go test -tags={{.BUILD_TAGS}} -timeout=0 -run=XXX
|
||||
-count={{.BENCH_COUNT}} -bench={{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-postgresql
|
||||
-postgresql-url=postgres://username@127.0.0.1:5432/ferretdb
|
||||
-disable-pushdown
|
||||
| tee new-postgresql.txt
|
||||
- ../bin/benchstat{{exeExt}} old-postgresql.txt new-postgresql.txt
|
||||
|
||||
bench-sqlite:
|
||||
desc: "Run benchmarks for `sqlite` backend"
|
||||
cmds:
|
||||
- >
|
||||
go test -tags={{.BUILD_TAGS}} -timeout=0 -run=XXX
|
||||
-count={{.BENCH_COUNT}} -bench={{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-sqlite
|
||||
-sqlite-url=file:../tmp/sqlite-tests/
|
||||
| tee new-sqlite.txt
|
||||
- ../bin/benchstat{{exeExt}} old-sqlite.txt new-sqlite.txt
|
||||
|
||||
bench-sqlite-no-pushdown:
|
||||
desc: "Run benchmarks for `sqlite` with filter pushdown disabled"
|
||||
cmds:
|
||||
- >
|
||||
go test -tags={{.BUILD_TAGS}} -timeout=0 -run=XXX
|
||||
-count={{.BENCH_COUNT}} -bench={{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-sqlite
|
||||
-sqlite-url=file:../tmp/sqlite-tests/
|
||||
-disable-pushdown
|
||||
| tee new-sqlite.txt
|
||||
- ../bin/benchstat{{exeExt}} old-sqlite.txt new-sqlite.txt
|
||||
|
||||
bench-mysql:
|
||||
desc: "Run benchmarks for `mysql` backend"
|
||||
cmds:
|
||||
- >
|
||||
go test -tags={{.BUILD_TAGS}} -timeout=0 -run=XXX
|
||||
-count={{.BENCH_COUNT}} -bench-{{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-mysql
|
||||
-mysql-url=mysql://username:password@127.0.0.1:3306/ferretdb
|
||||
| tee new-mysql.txt
|
||||
- ../bin/benchstat{{exeExt}} old-mysql.txt new-mysql.txt
|
||||
|
||||
bench-mysql-no-pushdown:
|
||||
desc: "Run benchmarks for `mysql` with filter pushdown disabled"
|
||||
cmds:
|
||||
- >
|
||||
go test -tags={{.BUILD_TAGS}} -timeout=0 -run=XXX
|
||||
-count={{.BENCH_COUNT}} -bench={{.BENCH_NAME}} -benchtime={{.BENCH_TIME}} -benchmem
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=ferretdb-mysql
|
||||
-mysql-url=mysql://username:password@127.0.0.1:3306/ferretdb
|
||||
-disable-pushdown
|
||||
| tee new-sqlite.txt
|
||||
- ../bin/benchstat{{exeExt}} old-sqlite.txt new-sqlite.txt
|
||||
|
||||
bench-mongodb:
|
||||
desc: "Run benchmarks for MongoDB"
|
||||
cmds:
|
||||
@@ -132,6 +63,6 @@ tasks:
|
||||
-log-level=error
|
||||
-bench-docs={{.BENCH_DOCS}}
|
||||
-target-backend=mongodb
|
||||
-target-url='mongodb://127.0.0.1:47017/'
|
||||
-target-url='mongodb://username:password@127.0.0.1:47017/'
|
||||
| tee new-mongodb.txt
|
||||
- ../bin/benchstat{{exeExt}} old-mongodb.txt new-mongodb.txt
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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 admin contains tests for administration commands:
|
||||
// - collMod;
|
||||
// - compact;
|
||||
// - create;
|
||||
// - createIndexes;
|
||||
// - currentOp;
|
||||
// - drop;
|
||||
// - dropDatabase;
|
||||
// - dropIndexes;
|
||||
// - getParameter;
|
||||
// - killOp;
|
||||
// - listCollections;
|
||||
// - listDatabases;
|
||||
// - listIndexes;
|
||||
// - renameCollection;
|
||||
// - setParameter.
|
||||
package admin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup.Main(m)
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
// 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 integration
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateCommandCollStats(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(tt, shareddata.ArrayDocuments)
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
||||
pipeline bson.A // required
|
||||
expected bson.D // optional, defaults to the standard response with storageStats
|
||||
expectedSizeIsZero bool // optional, if true, the size field is expected to be 0 (due to scale factor)
|
||||
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"EmptyCollStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{}}}},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
expected: bson.D{
|
||||
{
|
||||
"ns", collection.Database().Name() + "." + collection.Name(),
|
||||
},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
},
|
||||
},
|
||||
"Count": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"count", bson.D{}}}}}},
|
||||
expected: bson.D{
|
||||
{
|
||||
"ns", collection.Database().Name() + "." + collection.Name(),
|
||||
},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
{"count", int32(4)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
},
|
||||
"StorageStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}},
|
||||
expected: bson.D{
|
||||
{"ns", collection.Database().Name() + "." + collection.Name()},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
{"storageStats", bson.D{
|
||||
{"size", int32(0)},
|
||||
{"count", int32(0)},
|
||||
{"avgObjSize", int32(0)},
|
||||
{"numOrphanDocs", int32(0)},
|
||||
{"storageSize", int32(0)},
|
||||
{"freeStorageSize", int32(0)},
|
||||
{"capped", false},
|
||||
{"nindexes", int32(0)},
|
||||
{"indexDetails", bson.D{}},
|
||||
{"indexBuilds", bson.A{}},
|
||||
{"totalIndexSize", int32(0)},
|
||||
{"indexSizes", bson.D{}},
|
||||
{"totalSize", int32(0)},
|
||||
{"scaleFactor", int32(1)},
|
||||
}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
},
|
||||
"StorageStatsWithScale": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", 1000}}}}}}},
|
||||
expected: bson.D{
|
||||
{"ns", collection.Database().Name() + "." + collection.Name()},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
{"storageStats", bson.D{
|
||||
{"size", int32(0)},
|
||||
{"count", int32(0)},
|
||||
{"avgObjSize", int32(0)},
|
||||
{"numOrphanDocs", int32(0)},
|
||||
{"storageSize", int32(0)},
|
||||
{"freeStorageSize", int32(0)},
|
||||
{"capped", false},
|
||||
{"nindexes", int32(0)},
|
||||
{"indexDetails", bson.D{}},
|
||||
{"indexBuilds", bson.A{}},
|
||||
{"totalIndexSize", int32(0)},
|
||||
{"indexSizes", bson.D{}},
|
||||
{"totalSize", int32(0)},
|
||||
{"scaleFactor", int32(1000)},
|
||||
}},
|
||||
},
|
||||
expectedSizeIsZero: true,
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
},
|
||||
"StorageStatsFloatScale": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", 42.42}}}}}}},
|
||||
expected: bson.D{
|
||||
{"ns", collection.Database().Name() + "." + collection.Name()},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
{"storageStats", bson.D{
|
||||
{"size", int32(0)},
|
||||
{"count", int32(0)},
|
||||
{"avgObjSize", int32(0)},
|
||||
{"numOrphanDocs", int32(0)},
|
||||
{"storageSize", int32(0)},
|
||||
{"freeStorageSize", int32(0)},
|
||||
{"capped", false},
|
||||
{"nindexes", int32(0)},
|
||||
{"indexDetails", bson.D{}},
|
||||
{"indexBuilds", bson.A{}},
|
||||
{"totalIndexSize", int32(0)},
|
||||
{"indexSizes", bson.D{}},
|
||||
{"totalSize", int32(0)},
|
||||
{"scaleFactor", int32(42)},
|
||||
}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
},
|
||||
"CountAndStorageStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}}},
|
||||
expected: bson.D{
|
||||
{"ns", collection.Database().Name() + "." + collection.Name()},
|
||||
{"host", ""},
|
||||
{"localTime", primitive.DateTime(0)},
|
||||
{"storageStats", bson.D{
|
||||
{"size", int32(0)},
|
||||
{"count", int32(0)},
|
||||
{"avgObjSize", int32(0)},
|
||||
{"numOrphanDocs", int32(0)},
|
||||
{"storageSize", int32(0)},
|
||||
{"freeStorageSize", int32(0)},
|
||||
{"capped", false},
|
||||
{"nindexes", int32(0)},
|
||||
{"indexDetails", bson.D{}},
|
||||
{"indexBuilds", bson.A{}},
|
||||
{"totalIndexSize", int32(0)},
|
||||
{"indexSizes", bson.D{}},
|
||||
{"totalSize", int32(0)},
|
||||
{"scaleFactor", int32(1)},
|
||||
}},
|
||||
{"count", int32(4)},
|
||||
},
|
||||
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/534",
|
||||
},
|
||||
"CollStatsCount": {
|
||||
pipeline: bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}},
|
||||
bson.D{{"$count", "after"}},
|
||||
},
|
||||
expected: bson.D{{"after", int32(1)}},
|
||||
},
|
||||
} {
|
||||
tt.Run(name, func(tt *testing.T) {
|
||||
var t testing.TB = tt
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
require.NotNil(tt, tc.pipeline, "pipeline must be set")
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
// call validate to updated statistics
|
||||
err := collection.Database().RunCommand(ctx, bson.D{{"validate", collection.Name()}}).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
cursor, err := collection.Aggregate(ctx, tc.pipeline)
|
||||
require.NoError(t, err)
|
||||
|
||||
res := FetchAll(t, ctx, cursor)
|
||||
require.Len(t, res, 1)
|
||||
|
||||
var actualComparable bson.D
|
||||
|
||||
for _, field := range res[0] {
|
||||
switch field.Key {
|
||||
case "host":
|
||||
var port string
|
||||
_, port, err = net.SplitHostPort(field.Value.(string))
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, port)
|
||||
|
||||
actualComparable = append(actualComparable, bson.E{Key: field.Key, Value: ""})
|
||||
|
||||
case "localTime":
|
||||
assert.IsType(t, primitive.DateTime(0), field.Value)
|
||||
actualComparable = append(actualComparable, bson.E{Key: field.Key, Value: primitive.DateTime(0)})
|
||||
|
||||
case "storageStats":
|
||||
storageStats := field.Value.(bson.D)
|
||||
require.NotEmpty(t, storageStats)
|
||||
|
||||
var statsComparable bson.D
|
||||
|
||||
for _, stat := range storageStats {
|
||||
switch stat.Key {
|
||||
case "freeStorageSize":
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: int32(0)})
|
||||
|
||||
case "size":
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/556
|
||||
size, ok := stat.Value.(int32)
|
||||
require.True(t, ok)
|
||||
|
||||
if tc.expectedSizeIsZero {
|
||||
assert.Zero(t, size)
|
||||
} else {
|
||||
assert.Greater(t, size, int32(0))
|
||||
}
|
||||
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: int32(0)})
|
||||
|
||||
case "count", "avgObjSize", "storageSize", "nindexes", "totalIndexSize", "totalSize":
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/556
|
||||
val, ok := stat.Value.(int32)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Greater(t, val, int32(0), "field %s", stat.Key)
|
||||
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: int32(0)})
|
||||
|
||||
case "indexSizes":
|
||||
assert.NotEmpty(t, stat.Value.(bson.D))
|
||||
|
||||
for _, indexSize := range stat.Value.(bson.D) {
|
||||
assert.Greater(t, indexSize.Value, int32(0))
|
||||
}
|
||||
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: bson.D{}})
|
||||
|
||||
case "indexDetails":
|
||||
assert.NotEmpty(t, stat.Value.(bson.D))
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: bson.D{}})
|
||||
|
||||
case "indexBuilds":
|
||||
assert.IsType(t, bson.A{}, stat.Value)
|
||||
statsComparable = append(statsComparable, bson.E{Key: stat.Key, Value: bson.A{}})
|
||||
|
||||
case "wiredTiger":
|
||||
// exclusive to MongoDB
|
||||
|
||||
default:
|
||||
statsComparable = append(statsComparable, stat)
|
||||
}
|
||||
}
|
||||
|
||||
actualComparable = append(actualComparable, bson.E{Key: field.Key, Value: statsComparable})
|
||||
|
||||
default:
|
||||
actualComparable = append(actualComparable, field)
|
||||
}
|
||||
}
|
||||
|
||||
AssertEqualDocuments(t, tc.expected, actualComparable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateCommandCollStatsErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
command bson.D // required, command to run
|
||||
database *mongo.Database // defaults to collection.Database()
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NonExistentDatabase": {
|
||||
database: collection.Database().Client().Database("non-existent"),
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 26,
|
||||
Name: "NamespaceNotFound",
|
||||
Message: `PlanExecutor error during aggregation :: caused by :: ` +
|
||||
`Unable to retrieve storageStats in $collStats stage :: caused by :: ` +
|
||||
`Collection [non-existent.TestAggregateCommandCollStatsErrors] not found.`,
|
||||
},
|
||||
altMessage: "Collection [non-existent.TestAggregateCommandCollStatsErrors] not found.",
|
||||
},
|
||||
"NonExistentCollection": {
|
||||
command: bson.D{
|
||||
{"aggregate", "non-existent"},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 26,
|
||||
Name: "NamespaceNotFound",
|
||||
Message: `PlanExecutor error during aggregation :: caused by :: ` +
|
||||
`Unable to retrieve storageStats in $collStats stage :: caused by :: ` +
|
||||
`Collection [TestAggregateCommandCollStatsErrors.non-existent] not found.`,
|
||||
},
|
||||
altMessage: "Collection [TestAggregateCommandCollStatsErrors.non-existent] not found.",
|
||||
},
|
||||
"NilCollStats": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", nil}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 5447000,
|
||||
Name: "Location5447000",
|
||||
Message: `$collStats must take a nested object but found: $collStats: null`,
|
||||
},
|
||||
altMessage: `$collStats must take a nested object but found: { $collStats: null }`,
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
||||
},
|
||||
"StorageStatsNegativeScale": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", -1000}}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 51024,
|
||||
Name: "Location51024",
|
||||
Message: `BSON field 'scale' value must be >= 1, actual value '-1000'`,
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/536",
|
||||
},
|
||||
"StorageStatsInvalidScale": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", "invalid"}}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: `BSON field '$collStats.storageStats.scale' is the wrong type 'string', expected types '[long, int, decimal, double']`,
|
||||
},
|
||||
altMessage: `BSON field '$collStats.storageStats.scale' is the wrong type 'string', expected types '[long, int, decimal, double]'`,
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/536",
|
||||
},
|
||||
"CountCollStatsCount": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{
|
||||
bson.D{{"$count", "before"}},
|
||||
bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}},
|
||||
bson.D{{"$count", "after"}},
|
||||
}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 40602,
|
||||
Name: "Location40602",
|
||||
Message: `$collStats is only valid as the first stage in a pipeline`,
|
||||
},
|
||||
altMessage: `$collStats is only valid as the first stage in the pipeline.`,
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.err, "err must not be nil")
|
||||
|
||||
db := tc.database
|
||||
if db == nil {
|
||||
db = collection.Database()
|
||||
}
|
||||
|
||||
var res bson.D
|
||||
err := db.RunCommand(ctx, tc.command).Decode(&res)
|
||||
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateCommandCollStatsIndexSizes(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/538")
|
||||
|
||||
ctx, collection := setup.Setup(tt, shareddata.DocumentsStrings)
|
||||
|
||||
cursorNoScale, err := collection.Aggregate(ctx, bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer cursorNoScale.Close(ctx)
|
||||
|
||||
scale := int32(1000)
|
||||
cursor, err := collection.Aggregate(ctx, bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", scale}}}}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
resNoScale := FetchAll(t, ctx, cursorNoScale)
|
||||
require.Equal(t, 1, len(resNoScale))
|
||||
|
||||
res := FetchAll(t, ctx, cursor)
|
||||
require.Equal(t, 1, len(res))
|
||||
|
||||
var resComparable bson.D
|
||||
|
||||
for _, field := range res[0] {
|
||||
switch field.Key {
|
||||
case "localTime":
|
||||
assert.IsType(t, primitive.DateTime(0), field.Value)
|
||||
resComparable = append(resComparable, bson.E{Key: field.Key, Value: primitive.DateTime(0)})
|
||||
|
||||
case "storageStats":
|
||||
storageStats := field.Value.(bson.D)
|
||||
require.NotNil(t, storageStats)
|
||||
|
||||
var storageStatsComparable bson.D
|
||||
|
||||
for _, stat := range storageStats {
|
||||
switch stat.Key {
|
||||
case "scaleFactor":
|
||||
assert.Equal(t, scale, stat.Value)
|
||||
storageStatsComparable = append(storageStatsComparable, bson.E{Key: stat.Key, Value: int32(1)})
|
||||
|
||||
default:
|
||||
storageStatsComparable = append(storageStatsComparable, stat)
|
||||
}
|
||||
}
|
||||
|
||||
resComparable = append(resComparable, bson.E{Key: field.Key, Value: storageStatsComparable})
|
||||
|
||||
default:
|
||||
resComparable = append(resComparable, field)
|
||||
}
|
||||
}
|
||||
|
||||
var resNoScaleComparable bson.D
|
||||
|
||||
for _, field := range resNoScale[0] {
|
||||
switch field.Key {
|
||||
case "localTime":
|
||||
assert.IsType(t, primitive.DateTime(0), field.Value)
|
||||
resNoScaleComparable = append(resNoScaleComparable, bson.E{Key: field.Key, Value: primitive.DateTime(0)})
|
||||
|
||||
case "storageStats":
|
||||
storageStats := field.Value.(bson.D)
|
||||
require.NotNil(t, storageStats)
|
||||
|
||||
var storageStatsComparable bson.D
|
||||
|
||||
for _, stat := range storageStats {
|
||||
switch stat.Key {
|
||||
case "size", "storageSize", "freeStorageSize", "totalIndexSize", "totalSize":
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/556
|
||||
sizeNoScale, ok := stat.Value.(int32)
|
||||
require.True(t, ok)
|
||||
|
||||
storageStatsComparable = append(storageStatsComparable, bson.E{Key: stat.Key, Value: sizeNoScale / scale})
|
||||
|
||||
case "indexSizes":
|
||||
var indexSizesComparable bson.D
|
||||
|
||||
for _, indexSizeNoScale := range stat.Value.(bson.D) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/556
|
||||
sizeNoScale, ok := indexSizeNoScale.Value.(int32)
|
||||
require.True(t, ok)
|
||||
|
||||
indexSizesComparable = append(indexSizesComparable, bson.E{Key: indexSizeNoScale.Key, Value: sizeNoScale / scale})
|
||||
}
|
||||
|
||||
storageStatsComparable = append(storageStatsComparable, bson.E{Key: stat.Key, Value: indexSizesComparable})
|
||||
|
||||
case "scaleFactor":
|
||||
assert.Equal(t, int32(1), stat.Value)
|
||||
storageStatsComparable = append(storageStatsComparable, stat)
|
||||
|
||||
default:
|
||||
storageStatsComparable = append(storageStatsComparable, stat)
|
||||
}
|
||||
}
|
||||
|
||||
resNoScaleComparable = append(resNoScaleComparable, bson.E{Key: field.Key, Value: storageStatsComparable})
|
||||
|
||||
default:
|
||||
resNoScaleComparable = append(resNoScaleComparable, field)
|
||||
}
|
||||
}
|
||||
|
||||
AssertEqualDocuments(t, resComparable, resNoScaleComparable)
|
||||
}
|
||||
+429
-98
File diff suppressed because it is too large.
Load diff
+6
@@ -18,6 +18,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateCompatMatchExpr(t *testing.T) {
|
||||
@@ -28,11 +30,15 @@ func TestAggregateCompatMatchExpr(t *testing.T) {
|
||||
pipeline: bson.A{
|
||||
bson.D{{"$match", bson.D{{"$expr", "$v"}}}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/362",
|
||||
failsProviders: []shareddata.Provider{shareddata.Decimal128s, shareddata.Doubles, shareddata.Int64s, shareddata.Scalars},
|
||||
},
|
||||
"Sum": {
|
||||
pipeline: bson.A{bson.D{{"$match", bson.D{
|
||||
{"$expr", bson.D{{"$sum", "$v"}}},
|
||||
}}}},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/362",
|
||||
failsProviders: []shareddata.Provider{shareddata.Decimal128s, shareddata.Doubles, shareddata.Int64s, shareddata.Scalars},
|
||||
},
|
||||
"Gt": {
|
||||
pipeline: bson.A{bson.D{{"$match", bson.D{
|
||||
@@ -1,181 +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 integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/types"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateCompatCollStats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{
|
||||
Providers: []shareddata.Provider{shareddata.ArrayDocuments},
|
||||
AddNonExistentCollection: true,
|
||||
})
|
||||
ctx, targetCollections, compatCollections := s.Ctx, s.TargetCollections, s.CompatCollections
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
pipeline bson.A // required
|
||||
|
||||
resultType compatTestCaseResultType // defaults to nonEmptyResult
|
||||
skip string // always skip this test case, must have issue number mentioned
|
||||
}{
|
||||
"EmptyCollStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{}}}},
|
||||
},
|
||||
"Count": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"count", bson.D{}}}}}},
|
||||
},
|
||||
"StorageStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}},
|
||||
},
|
||||
"StorageStatsWithScale": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", 1000}}}}}}},
|
||||
},
|
||||
"StorageStatsFloatScale": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", 42.42}}}}}}},
|
||||
},
|
||||
"CountAndStorageStats": {
|
||||
pipeline: bson.A{bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}}},
|
||||
},
|
||||
"CollStatsCount": {
|
||||
pipeline: bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}},
|
||||
bson.D{{"$count", "after"}},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must be set")
|
||||
|
||||
var nonEmptyResults bool
|
||||
for i := range targetCollections {
|
||||
targetCollection := targetCollections[i]
|
||||
compatCollection := compatCollections[i]
|
||||
t.Run(targetCollection.Name(), func(t *testing.T) {
|
||||
require.Equal(t, compatCollection.Name(), targetCollection.Name())
|
||||
|
||||
// call validate to updated statistics
|
||||
command := bson.D{{"validate", targetCollection.Name()}}
|
||||
targetErr := targetCollection.Database().RunCommand(ctx, command).Err()
|
||||
compatErr := compatCollection.Database().RunCommand(ctx, command).Err()
|
||||
|
||||
if targetErr != nil {
|
||||
t.Logf("Target error: %v", targetErr)
|
||||
t.Logf("Compat error: %v", compatErr)
|
||||
|
||||
// error messages are intentionally not compared
|
||||
AssertMatchesCommandError(t, compatErr, targetErr)
|
||||
}
|
||||
|
||||
targetCursor, targetErr := targetCollection.Aggregate(ctx, tc.pipeline)
|
||||
compatCursor, compatErr := compatCollection.Aggregate(ctx, tc.pipeline)
|
||||
|
||||
if targetCursor != nil {
|
||||
defer targetCursor.Close(ctx)
|
||||
}
|
||||
if compatCursor != nil {
|
||||
defer compatCursor.Close(ctx)
|
||||
}
|
||||
|
||||
if targetErr != nil {
|
||||
t.Logf("Target error: %v", targetErr)
|
||||
t.Logf("Compat error: %v", compatErr)
|
||||
|
||||
// error messages are intentionally not compared
|
||||
AssertMatchesCommandError(t, compatErr, targetErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, compatErr, "compat error; target returned no error")
|
||||
|
||||
targetRes := FetchAll(t, ctx, targetCursor)
|
||||
compatRes := FetchAll(t, ctx, compatCursor)
|
||||
|
||||
// $collStats returns one document per shard.
|
||||
require.Equal(t, 1, len(compatRes))
|
||||
require.Equal(t, 1, len(targetRes))
|
||||
|
||||
// Check the keys are the same
|
||||
targetKeys := CollectKeys(t, targetRes[0])
|
||||
compatKeys := CollectKeys(t, compatRes[0])
|
||||
|
||||
require.Equal(t, compatKeys, targetKeys)
|
||||
|
||||
if len(targetRes) > 0 || len(compatRes) > 0 {
|
||||
nonEmptyResults = true
|
||||
}
|
||||
|
||||
targetDoc := ConvertDocument(t, targetRes[0])
|
||||
compatDoc := ConvertDocument(t, compatRes[0])
|
||||
|
||||
targetNs, _ := targetDoc.Get("ns")
|
||||
compatNs, _ := compatDoc.Get("ns")
|
||||
require.Equal(t, compatNs, targetNs)
|
||||
|
||||
targetCount, _ := targetDoc.Get("count")
|
||||
compatCount, _ := compatDoc.Get("count")
|
||||
require.EqualValues(t, compatCount, targetCount)
|
||||
|
||||
targetStorageStatsV, _ := targetDoc.Get("storageStats")
|
||||
compatStorageStatsV, _ := compatDoc.Get("storageStats")
|
||||
|
||||
if targetStorageStatsV == nil {
|
||||
require.Equal(t, compatStorageStatsV, targetStorageStatsV)
|
||||
return
|
||||
}
|
||||
|
||||
targetStorageStats := targetStorageStatsV.(*types.Document)
|
||||
compatStorageStats := compatStorageStatsV.(*types.Document)
|
||||
|
||||
targetStorageCount, _ := targetStorageStats.Get("count")
|
||||
compatStorageCount, _ := compatStorageStats.Get("count")
|
||||
require.EqualValues(t, compatStorageCount, targetStorageCount)
|
||||
|
||||
targetStorageIndexes, _ := targetStorageStats.Get("nindexes")
|
||||
compatStorageIndexes, _ := compatStorageStats.Get("nindexes")
|
||||
require.EqualValues(t, compatStorageIndexes, targetStorageIndexes)
|
||||
|
||||
targetStorageScaleFactor, _ := targetStorageStats.Get("scaleFactor")
|
||||
compatStorageScaleFactor, _ := compatStorageStats.Get("scaleFactor")
|
||||
require.Equal(t, compatStorageScaleFactor, targetStorageScaleFactor)
|
||||
})
|
||||
}
|
||||
|
||||
switch tc.resultType {
|
||||
case nonEmptyResult:
|
||||
assert.True(t, nonEmptyResults, "expected non-empty results (some documents should be returned)")
|
||||
case emptyResult:
|
||||
assert.False(t, nonEmptyResults, "expected empty results (no documents should be returned)")
|
||||
default:
|
||||
t.Fatalf("unknown result type %v", tc.resultType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,313 +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 integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/types"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateCollStats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.DocumentsStrings)
|
||||
|
||||
// call validate to updated statistics
|
||||
err := collection.Database().RunCommand(ctx, bson.D{{"validate", collection.Name()}}).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
pipeline := bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}}
|
||||
|
||||
cursor, err := collection.Aggregate(ctx, pipeline)
|
||||
require.NoError(t, err)
|
||||
|
||||
res := FetchAll(t, ctx, cursor)
|
||||
require.Len(t, res, 1)
|
||||
doc := ConvertDocument(t, res[0])
|
||||
|
||||
assert.Equal(t, collection.Database().Name()+"."+collection.Name(), must.NotFail(doc.Get("ns")))
|
||||
|
||||
v, _ := doc.Get("storageStats")
|
||||
require.NotNil(t, v)
|
||||
|
||||
storageStats, ok := v.(*types.Document)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("size")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("count")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("avgObjSize")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("storageSize")))
|
||||
assert.Zero(t, must.NotFail(storageStats.Get("freeStorageSize")))
|
||||
assert.Equal(t, false, must.NotFail(storageStats.Get("capped")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("nindexes")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("totalIndexSize")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("totalSize")))
|
||||
assert.NotZero(t, must.NotFail(storageStats.Get("indexSizes")))
|
||||
assert.Equal(t, int32(1), must.NotFail(storageStats.Get("scaleFactor")))
|
||||
|
||||
cappedCollectionName := testutil.CollectionName(t) + "capped"
|
||||
opts := options.CreateCollection().SetCapped(true).SetSizeInBytes(1000).SetMaxDocuments(10)
|
||||
err = collection.Database().CreateCollection(ctx, cappedCollectionName, opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
cappedCollection := collection.Database().Collection(cappedCollectionName)
|
||||
cursor, err = cappedCollection.Aggregate(ctx, pipeline)
|
||||
require.NoError(t, err)
|
||||
|
||||
res = FetchAll(t, ctx, cursor)
|
||||
require.Len(t, res, 1)
|
||||
v, _ = ConvertDocument(t, res[0]).Get("storageStats")
|
||||
require.NotNil(t, v)
|
||||
|
||||
storageStats, ok = v.(*types.Document)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, true, must.NotFail(storageStats.Get("capped")))
|
||||
}
|
||||
|
||||
func TestAggregateCollStatsCommandErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
command bson.D // required, command to run
|
||||
database *mongo.Database // defaults to collection.Database()
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
}{
|
||||
"NonExistentDatabase": {
|
||||
database: collection.Database().Client().Database("non-existent"),
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 26,
|
||||
Name: "NamespaceNotFound",
|
||||
Message: `PlanExecutor error during aggregation :: caused by :: ` +
|
||||
`Unable to retrieve storageStats in $collStats stage :: caused by :: ` +
|
||||
`Collection [non-existent.TestAggregateCollStatsCommandErrors] not found.`,
|
||||
},
|
||||
altMessage: "ns not found: non-existent.TestAggregateCollStatsCommandErrors",
|
||||
},
|
||||
"NonExistentCollection": {
|
||||
command: bson.D{
|
||||
{"aggregate", "non-existent"},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 26,
|
||||
Name: "NamespaceNotFound",
|
||||
Message: `PlanExecutor error during aggregation :: caused by :: ` +
|
||||
`Unable to retrieve storageStats in $collStats stage :: caused by :: ` +
|
||||
`Collection [TestAggregateCollStatsCommandErrors.non-existent] not found.`,
|
||||
},
|
||||
altMessage: "ns not found: TestAggregateCollStatsCommandErrors.non-existent",
|
||||
},
|
||||
"NilCollStats": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", nil}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 5447000,
|
||||
Name: "Location5447000",
|
||||
Message: `$collStats must take a nested object but found: $collStats: null`,
|
||||
},
|
||||
altMessage: `$collStats must take a nested object but found: { $collStats: null }`,
|
||||
},
|
||||
"StorageStatsNegativeScale": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", -1000}}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 51024,
|
||||
Name: "Location51024",
|
||||
Message: `BSON field 'scale' value must be >= 1, actual value '-1000'`,
|
||||
},
|
||||
},
|
||||
"StorageStatsInvalidScale": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", "invalid"}}}}}}}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: `BSON field '$collStats.storageStats.scale' is the wrong type 'string', expected types '[long, int, decimal, double']`,
|
||||
},
|
||||
altMessage: `BSON field '$collStats.storageStats.scale' is the wrong type 'string', expected types '[long, int, decimal, double]'`,
|
||||
},
|
||||
"CountCollStatsCount": {
|
||||
command: bson.D{
|
||||
{"aggregate", collection.Name()},
|
||||
{"pipeline", bson.A{
|
||||
bson.D{{"$count", "before"}},
|
||||
bson.D{{"$collStats", bson.D{{"count", bson.D{}}, {"storageStats", bson.D{}}}}},
|
||||
bson.D{{"$count", "after"}},
|
||||
}},
|
||||
{"cursor", bson.D{}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 40602,
|
||||
Name: "Location40602",
|
||||
Message: `$collStats is only valid as the first stage in a pipeline`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.err, "err must not be nil")
|
||||
|
||||
db := tc.database
|
||||
if db == nil {
|
||||
db = collection.Database()
|
||||
}
|
||||
|
||||
var res bson.D
|
||||
err := db.RunCommand(ctx, tc.command).Decode(&res)
|
||||
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateCollStatsCommandIndexSizes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.DocumentsStrings)
|
||||
|
||||
cursorNoScale, err := collection.Aggregate(ctx, bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"storageStats", bson.D{}}}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer cursorNoScale.Close(ctx)
|
||||
|
||||
scale := int32(1000)
|
||||
cursor, err := collection.Aggregate(ctx, bson.A{
|
||||
bson.D{{"$collStats", bson.D{{"storageStats", bson.D{{"scale", scale}}}}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
resNoScale := FetchAll(t, ctx, cursorNoScale)
|
||||
require.Equal(t, 1, len(resNoScale))
|
||||
|
||||
res := FetchAll(t, ctx, cursor)
|
||||
require.Equal(t, 1, len(res))
|
||||
|
||||
docNoScale := ConvertDocument(t, resNoScale[0])
|
||||
doc := ConvertDocument(t, res[0])
|
||||
|
||||
storageStatsNoScale := must.NotFail(docNoScale.Get("storageStats")).(*types.Document)
|
||||
storageStats := must.NotFail(doc.Get("storageStats")).(*types.Document)
|
||||
|
||||
size := must.NotFail(storageStats.Get("size"))
|
||||
switch sizeNoScale := must.NotFail(storageStatsNoScale.Get("size")).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, size)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), size)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
|
||||
avgObjSizeNoScale := must.NotFail(storageStatsNoScale.Get("avgObjSize"))
|
||||
avgObjSize := must.NotFail(storageStats.Get("avgObjSize"))
|
||||
require.EqualValues(t, avgObjSizeNoScale, avgObjSize)
|
||||
|
||||
storageSize := must.NotFail(storageStats.Get("storageSize"))
|
||||
switch sizeNoScale := must.NotFail(storageStatsNoScale.Get("storageSize")).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, storageSize)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), storageSize)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
|
||||
freeStorageSize := must.NotFail(storageStats.Get("freeStorageSize"))
|
||||
switch sizeNoScale := must.NotFail(storageStatsNoScale.Get("freeStorageSize")).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, freeStorageSize)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), freeStorageSize)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
|
||||
totalIndexSize := must.NotFail(storageStats.Get("totalIndexSize"))
|
||||
switch sizeNoScale := must.NotFail(storageStatsNoScale.Get("totalIndexSize")).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, totalIndexSize)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), totalIndexSize)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
|
||||
totalSize := must.NotFail(storageStats.Get("totalSize"))
|
||||
switch sizeNoScale := must.NotFail(storageStatsNoScale.Get("totalSize")).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, totalSize)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), totalSize)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
|
||||
indexSizesNoScale := must.NotFail(storageStatsNoScale.Get("indexSizes")).(*types.Document)
|
||||
indexSizes := must.NotFail(storageStats.Get("indexSizes")).(*types.Document)
|
||||
|
||||
require.Equal(t, []string{"_id_"}, indexSizesNoScale.Keys())
|
||||
require.Equal(t, []string{"_id_"}, indexSizes.Keys())
|
||||
|
||||
for _, index := range indexSizesNoScale.Keys() {
|
||||
size := must.NotFail(indexSizes.Get(index))
|
||||
switch sizeNoScale := must.NotFail(indexSizesNoScale.Get(index)).(type) {
|
||||
case int32:
|
||||
require.EqualValues(t, sizeNoScale/scale, size)
|
||||
case int64:
|
||||
require.EqualValues(t, sizeNoScale/int64(scale), size)
|
||||
default:
|
||||
t.Fatalf("unknown type %v", sizeNoScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,10 @@ import (
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/must"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateAddFieldsErrors(t *testing.T) {
|
||||
@@ -36,9 +38,9 @@ func TestAggregateAddFieldsErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NotDocument": {
|
||||
pipeline: bson.A{
|
||||
@@ -49,7 +51,7 @@ func TestAggregateAddFieldsErrors(t *testing.T) {
|
||||
Name: "Location40272",
|
||||
Message: "$addFields specification stage must be an object, got string",
|
||||
},
|
||||
altMessage: "$addFields specification stage must be an object, got string",
|
||||
altMessage: "$addFields specification stage must be an object",
|
||||
},
|
||||
"InvalidFieldPath": {
|
||||
pipeline: bson.A{
|
||||
@@ -60,11 +62,16 @@ func TestAggregateAddFieldsErrors(t *testing.T) {
|
||||
Name: "Location16410",
|
||||
Message: "Invalid $addFields :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -82,6 +89,35 @@ func TestAggregateAddFieldsErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateGroupSumDecimalDouble(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/1056")
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
_, err := collection.InsertMany(ctx, bson.A{
|
||||
bson.D{{"_id", "decimal128"}, {"v", must.NotFail(primitive.ParseDecimal128("42.1"))}},
|
||||
bson.D{{"_id", "double"}, {"v", float64(42.1)}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cursor, err := collection.Aggregate(ctx, bson.A{
|
||||
bson.D{{"$group", bson.D{{"_id", nil}, {"sum", bson.D{{"$sum", "$v"}}}}}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var res bson.A
|
||||
require.NoError(t, cursor.All(ctx, &res))
|
||||
|
||||
expected := bson.A{bson.D{
|
||||
{"_id", nil},
|
||||
{"sum", primitive.NewDecimal128(3459220962935157325, 6906845732440572485)}, // 84.20000000000000142108547152020037
|
||||
}}
|
||||
|
||||
assert.Equal(t, expected, res)
|
||||
}
|
||||
|
||||
func TestAggregateGroupErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -90,9 +126,10 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct {
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1086
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"UnaryOperatorSum": {
|
||||
pipeline: bson.A{
|
||||
@@ -103,7 +140,8 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location40237",
|
||||
Message: "The $sum accumulator is a unary operator",
|
||||
},
|
||||
altMessage: "The $sum accumulator is a unary operator",
|
||||
altMessage: "The $sum accumulator is a unary operator",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/389",
|
||||
},
|
||||
"TypeEmpty": {
|
||||
pipeline: bson.A{
|
||||
@@ -114,6 +152,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location40234",
|
||||
Message: "The field 'v' must be an accumulator object",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/389",
|
||||
},
|
||||
"TwoOperators": {
|
||||
pipeline: bson.A{
|
||||
@@ -124,7 +163,8 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location15983",
|
||||
Message: "An object representing an expression must have exactly one field: { $type: 42, $op: 42 }",
|
||||
},
|
||||
altMessage: "An object representing an expression must have exactly one field",
|
||||
altMessage: "An object representing an expression must have exactly one field",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"TypeInvalidLen": {
|
||||
pipeline: bson.A{
|
||||
@@ -135,6 +175,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location16020",
|
||||
Message: "Expression $type takes exactly 1 arguments. 2 were passed in.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"NonExistentOperator": {
|
||||
pipeline: bson.A{
|
||||
@@ -145,6 +186,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "InvalidPipelineOperator",
|
||||
Message: "Unrecognized expression '$non-existent'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"SumEmptyExpression": {
|
||||
pipeline: bson.A{
|
||||
@@ -157,6 +199,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location16872",
|
||||
Message: "'$' by itself is not a valid FieldPath",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"SumEmptyVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -169,6 +212,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "empty variable names are not allowed",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"SumDollarVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -181,6 +225,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "'$' starts with an invalid character for a user variable name",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"RecursiveNonExistentOperator": {
|
||||
pipeline: bson.A{
|
||||
@@ -191,6 +236,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "InvalidPipelineOperator",
|
||||
Message: "Unrecognized expression '$non-existent'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionDuplicateFields": {
|
||||
pipeline: bson.A{
|
||||
@@ -206,6 +252,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location16406",
|
||||
Message: "duplicate field name specified in object literal: { v: \"$v\", v: \"$non-existent\" }",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionEmptyPath": {
|
||||
pipeline: bson.A{
|
||||
@@ -218,6 +265,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "Location16872",
|
||||
Message: "'$' by itself is not a valid FieldPath",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionEmptyVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -230,6 +278,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "empty variable names are not allowed",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionInvalidVariable$": {
|
||||
pipeline: bson.A{
|
||||
@@ -242,6 +291,7 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "'$' starts with an invalid character for a user variable name",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionInvalidVariable$s": {
|
||||
pipeline: bson.A{
|
||||
@@ -254,7 +304,8 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "'$s' starts with an invalid character for a user variable name",
|
||||
},
|
||||
altMessage: "'$' starts with an invalid character for a user variable name",
|
||||
altMessage: "'$' starts with an invalid character for a user variable name",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/390",
|
||||
},
|
||||
"IDExpressionNonExistingVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -270,9 +321,17 @@ func TestAggregateGroupErrors(t *testing.T) {
|
||||
skip: "https://github.com/FerretDB/FerretDB/issues/2275",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
tt.Skip(tc.skip)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -294,9 +353,10 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct {
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/1086
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"EmptyPipeline": {
|
||||
pipeline: bson.A{
|
||||
@@ -307,6 +367,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location51272",
|
||||
Message: "Invalid $project :: caused by :: projection specification must have at least one field",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"EmptyProjectionField": {
|
||||
pipeline: bson.A{
|
||||
@@ -329,6 +390,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"FieldPath cannot be constructed with empty string",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"EmptyPath": {
|
||||
pipeline: bson.A{
|
||||
@@ -339,6 +401,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "Invalid $project :: caused by :: FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ExcludeInclude": {
|
||||
pipeline: bson.A{
|
||||
@@ -349,7 +412,8 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location31253",
|
||||
Message: "Invalid $project :: caused by :: Cannot do inclusion on field bar in exclusion projection",
|
||||
},
|
||||
altMessage: "Cannot do inclusion on field bar in exclusion projection",
|
||||
altMessage: "Cannot do inclusion on field bar in exclusion projection",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"IncludeExclude": {
|
||||
pipeline: bson.A{
|
||||
@@ -360,7 +424,8 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location31254",
|
||||
Message: "Invalid $project :: caused by :: Cannot do exclusion on field bar in inclusion projection",
|
||||
},
|
||||
altMessage: "Cannot do exclusion on field bar in inclusion projection",
|
||||
altMessage: "Cannot do exclusion on field bar in inclusion projection",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorMultiple": {
|
||||
pipeline: bson.A{
|
||||
@@ -372,6 +437,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"Cannot use positional projection in aggregation projection",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorMiddle": {
|
||||
pipeline: bson.A{
|
||||
@@ -392,6 +458,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
"for example: a.b.$. If the query previously used a form " +
|
||||
"like a.b.$.d, remove the parts following the '$' and " +
|
||||
"the results will be equivalent.",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorWrongLocations": {
|
||||
pipeline: bson.A{
|
||||
@@ -412,6 +479,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
"for example: a.b.$. If the query previously used a form " +
|
||||
"like a.b.$.d, remove the parts following the '$' and " +
|
||||
"the results will be equivalent.",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorEmptyPath": {
|
||||
pipeline: bson.A{
|
||||
@@ -423,6 +491,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"Cannot use positional projection in aggregation projection",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorDollarKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -434,6 +503,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorDollarInKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -445,6 +515,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorDollarPrefix": {
|
||||
pipeline: bson.A{
|
||||
@@ -456,6 +527,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorDotDollarInKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -467,6 +539,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorPrefixSuffix": {
|
||||
pipeline: bson.A{
|
||||
@@ -478,6 +551,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"Cannot use positional projection in aggregation projection",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"PositionalOperatorExclusion": {
|
||||
pipeline: bson.A{
|
||||
@@ -489,6 +563,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Message: "Invalid $project :: caused by :: " +
|
||||
"Cannot use positional projection in aggregation projection",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectPositionalOperator": {
|
||||
pipeline: bson.A{
|
||||
@@ -499,6 +574,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location31324",
|
||||
Message: "Invalid $project :: caused by :: Cannot use positional projection in aggregation projection",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectTypeEmpty": {
|
||||
pipeline: bson.A{
|
||||
@@ -509,6 +585,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location51270",
|
||||
Message: "Invalid $project :: caused by :: An empty sub-projection is not a valid value." + " Found empty object at path",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectTwoOperators": {
|
||||
pipeline: bson.A{
|
||||
@@ -519,6 +596,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location16410",
|
||||
Message: "Invalid $project :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectTypeInvalidLen": {
|
||||
pipeline: bson.A{
|
||||
@@ -529,6 +607,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location16020",
|
||||
Message: "Invalid $project :: caused by :: Expression $type takes exactly 1 arguments. 2 were passed in.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectNonExistentOperator": {
|
||||
pipeline: bson.A{
|
||||
@@ -540,6 +619,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location31325",
|
||||
Message: "Invalid $project :: caused by :: Unknown expression $non-existent",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"ProjectRecursiveNonExistentOperator": {
|
||||
pipeline: bson.A{
|
||||
@@ -550,6 +630,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "InvalidPipelineOperator",
|
||||
Message: "Invalid $project :: caused by :: Unrecognized expression '$non-existent'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"SumEmptyExpression": {
|
||||
pipeline: bson.A{
|
||||
@@ -562,6 +643,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "Location16872",
|
||||
Message: "Invalid $project :: caused by :: '$' by itself is not a valid FieldPath",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"SumEmptyVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -574,6 +656,7 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "Invalid $project :: caused by :: empty variable names are not allowed",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
"SumDollarVariable": {
|
||||
pipeline: bson.A{
|
||||
@@ -586,11 +669,20 @@ func TestAggregateProjectErrors(t *testing.T) {
|
||||
Name: "FailedToParse",
|
||||
Message: "Invalid $project :: caused by :: '$' starts with an invalid character for a user variable name",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/369",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
tt.Skip(tc.skip)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -610,8 +702,7 @@ func TestAggregateProject(t *testing.T) {
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
res []bson.D // required, expected response
|
||||
skip string // optional, skip test with a specified reason
|
||||
res []bson.D // required, expected response
|
||||
}{
|
||||
"IDFalseValueTrue": {
|
||||
pipeline: bson.A{
|
||||
@@ -629,9 +720,7 @@ func TestAggregateProject(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
require.NotNil(t, tc.res, "res must not be nil")
|
||||
@@ -656,9 +745,9 @@ func TestAggregateSetErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NotDocument": {
|
||||
pipeline: bson.A{
|
||||
@@ -669,7 +758,7 @@ func TestAggregateSetErrors(t *testing.T) {
|
||||
Name: "Location40272",
|
||||
Message: "$set specification stage must be an object, got string",
|
||||
},
|
||||
altMessage: "$set specification stage must be an object, got string",
|
||||
altMessage: "$addFields specification stage must be an object",
|
||||
},
|
||||
"InvalidFieldPath": {
|
||||
pipeline: bson.A{
|
||||
@@ -680,11 +769,16 @@ func TestAggregateSetErrors(t *testing.T) {
|
||||
Name: "Location16410",
|
||||
Message: "Invalid $set :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -704,9 +798,9 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"EmptyString": {
|
||||
pipeline: bson.A{
|
||||
@@ -717,6 +811,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location40352",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath cannot be constructed with empty string",
|
||||
},
|
||||
altMessage: "FieldPath cannot be constructed with empty string",
|
||||
},
|
||||
"InvalidType": {
|
||||
pipeline: bson.A{
|
||||
@@ -737,6 +832,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"PathEmptySuffixKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -747,6 +843,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location40353",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath must not end with a '.'.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"PathEmptyPrefixKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -757,6 +854,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"PathDollarPrefix": {
|
||||
pipeline: bson.A{
|
||||
@@ -767,6 +865,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location16410",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
altMessage: "FieldPath field names may not start with '$'",
|
||||
},
|
||||
"ArrayEmpty": {
|
||||
pipeline: bson.A{
|
||||
@@ -798,6 +897,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location40352",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath cannot be constructed with empty string",
|
||||
},
|
||||
altMessage: "FieldPath cannot be constructed with empty string",
|
||||
},
|
||||
"ArrayPathDuplicate": {
|
||||
pipeline: bson.A{
|
||||
@@ -808,6 +908,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location31250",
|
||||
Message: "Invalid $unset :: caused by :: Path collision at v",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathOverwrites": {
|
||||
pipeline: bson.A{
|
||||
@@ -818,6 +919,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location31249",
|
||||
Message: "Invalid $unset :: caused by :: Path collision at v.foo remaining portion foo",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathOverwritesRemaining": {
|
||||
pipeline: bson.A{
|
||||
@@ -828,6 +930,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location31249",
|
||||
Message: "Invalid $unset :: caused by :: Path collision at v.foo.bar remaining portion foo.bar",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathCollision": {
|
||||
pipeline: bson.A{
|
||||
@@ -838,6 +941,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location31250",
|
||||
Message: "Invalid $unset :: caused by :: Path collision at v",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathEmptyKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -848,6 +952,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathEmptySuffixKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -858,6 +963,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location40353",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath must not end with a '.'.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathEmptyPrefixKey": {
|
||||
pipeline: bson.A{
|
||||
@@ -868,6 +974,7 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"ArrayPathDollarPrefix": {
|
||||
pipeline: bson.A{
|
||||
@@ -878,11 +985,16 @@ func TestAggregateUnsetErrors(t *testing.T) {
|
||||
Name: "Location16410",
|
||||
Message: "Invalid $unset :: caused by :: FieldPath field names may not start with '$'. Consider using $getField or $setField.",
|
||||
},
|
||||
altMessage: "FieldPath field names may not start with '$'",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -902,9 +1014,9 @@ func TestAggregateSortErrors(t *testing.T) {
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for test only
|
||||
pipeline bson.A // required, aggregation pipeline stages
|
||||
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required
|
||||
altMessage string // optional, alternative error message
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"DotNotationMissingField": {
|
||||
pipeline: bson.A{bson.D{{"$sort", bson.D{
|
||||
@@ -915,11 +1027,16 @@ func TestAggregateSortErrors(t *testing.T) {
|
||||
Name: "Location15998",
|
||||
Message: "FieldPath field names may not be empty strings.",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.pipeline, "pipeline must not be nil")
|
||||
@@ -933,15 +1050,14 @@ func TestAggregateSortErrors(t *testing.T) {
|
||||
|
||||
func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // used for testing only
|
||||
command bson.D // required, command to run
|
||||
|
||||
err *mongo.CommandError // required, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // required, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NegativeLong": {
|
||||
command: bson.D{
|
||||
@@ -955,6 +1071,7 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"MaxLong": {
|
||||
command: bson.D{
|
||||
@@ -968,7 +1085,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "BadValue",
|
||||
Message: "9223372036854775807 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
||||
},
|
||||
altMessage: "9223372036854775807 value for maxTimeMS is out of range",
|
||||
altMessage: "9223372036854775807 value for maxTimeMS is out of range",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"Double": {
|
||||
command: bson.D{
|
||||
@@ -982,6 +1100,7 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "BadValue",
|
||||
Message: "maxTimeMS has non-integral value",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"NegativeDouble": {
|
||||
command: bson.D{
|
||||
@@ -995,7 +1114,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-14245345234123246'",
|
||||
},
|
||||
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.424534523412325e+16'",
|
||||
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.424534523412325e+16'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"BigDouble": {
|
||||
command: bson.D{
|
||||
@@ -1009,7 +1129,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "BadValue",
|
||||
Message: "9223372036854775807 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
||||
},
|
||||
altMessage: "1.797693134862316e+308 value for maxTimeMS is out of range",
|
||||
altMessage: "1.797693134862316e+308 value for maxTimeMS is out of range",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"BigNegativeDouble": {
|
||||
command: bson.D{
|
||||
@@ -1023,7 +1144,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-9223372036854775808'",
|
||||
},
|
||||
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.797693134862316e+308'",
|
||||
altMessage: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1.797693134862316e+308'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"NegativeInt32": {
|
||||
command: bson.D{
|
||||
@@ -1037,6 +1159,7 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'maxTimeMS' value must be >= 0, actual value '-1123123'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"MaxIntPlus": {
|
||||
command: bson.D{
|
||||
@@ -1050,7 +1173,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "BadValue",
|
||||
Message: "2147483648 value for maxTimeMS is out of range " + shareddata.Int32Interval,
|
||||
},
|
||||
altMessage: "2147483648 value for maxTimeMS is out of range",
|
||||
altMessage: "2147483648 value for maxTimeMS is out of range",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"Null": {
|
||||
command: bson.D{
|
||||
@@ -1064,6 +1188,7 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "BadValue",
|
||||
Message: "maxTimeMS must be a number",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"String": {
|
||||
command: bson.D{
|
||||
@@ -1077,7 +1202,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'string', expected types '[long, int, decimal, double']",
|
||||
},
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'string', expected types '[long, int, decimal, double]'",
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'string', expected types '[long, int, decimal, double]'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"Array": {
|
||||
command: bson.D{
|
||||
@@ -1091,7 +1217,8 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'array', expected types '[long, int, decimal, double']",
|
||||
},
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'array', expected types '[long, int, decimal, double]'",
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'array', expected types '[long, int, decimal, double]'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
"Document": {
|
||||
command: bson.D{
|
||||
@@ -1105,12 +1232,17 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'aggregate.maxTimeMS' is the wrong type 'object', expected types '[long, int, decimal, double']",
|
||||
},
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'object', expected types '[long, int, decimal, double]'",
|
||||
altMessage: "BSON field 'aggregate.maxTimeMS' is the wrong type 'object', expected types '[long, int, decimal, double]'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/354",
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
require.NotNil(t, tc.err, "err must not be nil")
|
||||
@@ -1125,12 +1257,11 @@ func TestAggregateCommandMaxTimeMSErrors(t *testing.T) {
|
||||
|
||||
func TestAggregateCommandCursor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
// the number of documents is set above the default batchSize of 101
|
||||
// for testing unset batchSize returning default batchSize
|
||||
arr, _ := GenerateDocuments(0, 110)
|
||||
arr := GenerateDocuments(0, 110)
|
||||
_, err := collection.InsertMany(ctx, arr)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1138,10 +1269,10 @@ func TestAggregateCommandCursor(t *testing.T) {
|
||||
pipeline any // optional, defaults to bson.A{}
|
||||
cursor any // optional, nil to leave cursor unset
|
||||
|
||||
firstBatch primitive.A // optional, expected firstBatch
|
||||
err *mongo.CommandError // optional, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // optional, skip test with a specified reason
|
||||
firstBatch primitive.A // optional, expected firstBatch
|
||||
err *mongo.CommandError // optional, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"Int": {
|
||||
cursor: bson.D{{"batchSize", 1}},
|
||||
@@ -1162,7 +1293,8 @@ func TestAggregateCommandCursor(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'",
|
||||
},
|
||||
altMessage: "BSON field 'batchSize' value must be >= 0, actual value '-1'",
|
||||
altMessage: "BSON field 'batchSize' value must be >= 0, actual value '-1'",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
||||
},
|
||||
"DoubleZero": {
|
||||
cursor: bson.D{{"batchSize", float64(0)}},
|
||||
@@ -1175,6 +1307,7 @@ func TestAggregateCommandCursor(t *testing.T) {
|
||||
Name: "Location51024",
|
||||
Message: "BSON field 'batchSize' value must be >= 0, actual value '-1'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/349",
|
||||
},
|
||||
"DoubleFloor": {
|
||||
cursor: bson.D{{"batchSize", 1.9}},
|
||||
@@ -1188,7 +1321,7 @@ func TestAggregateCommandCursor(t *testing.T) {
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'cursor.batchSize' is the wrong type 'bool', expected types '[long, int, decimal, double']",
|
||||
},
|
||||
altMessage: "BSON field 'aggregate.batchSize' is the wrong type 'bool', expected types '[long, int, decimal, double]'",
|
||||
altMessage: "BSON field 'cursor.batchSize' is the wrong type 'bool', expected type 'number'",
|
||||
},
|
||||
"Unset": {
|
||||
cursor: nil,
|
||||
@@ -1225,9 +1358,13 @@ func TestAggregateCommandCursor(t *testing.T) {
|
||||
firstBatch: arr[:6],
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
var pipeline any = bson.A{}
|
||||
@@ -19,10 +19,10 @@ import (
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestAggregateVariablesCompatRoot(t *testing.T) {
|
||||
func TestAggregateCompatRootVariable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
providers := shareddata.AllProviders().Remove(shareddata.Composites)
|
||||
@@ -32,7 +32,6 @@ func TestAggregateVariablesCompatRoot(t *testing.T) {
|
||||
pipeline: bson.A{
|
||||
bson.D{{"$addFields", bson.D{{"field", "$$ROOT"}}}},
|
||||
},
|
||||
skip: "https://github.com/FerretDB/FerretDB/issues/1413",
|
||||
},
|
||||
"GroupID": {
|
||||
pipeline: bson.A{
|
||||
@@ -125,7 +124,6 @@ func TestAggregateVariablesCompatRoot(t *testing.T) {
|
||||
pipeline: bson.A{
|
||||
bson.D{{"$set", bson.D{{"field", "$$ROOT"}}}},
|
||||
},
|
||||
skip: "https://github.com/FerretDB/FerretDB/issues/1413",
|
||||
},
|
||||
"Unwind": {
|
||||
pipeline: bson.A{
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package oplog
|
||||
// Package aggregation contains tests for the aggregate command.
|
||||
package aggregation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -0,0 +1,392 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestCreateUserCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupWithOpts(t, nil)
|
||||
ctx, db := s.Ctx, s.Collection.Database()
|
||||
|
||||
testCases := map[string]struct { //nolint:vet // for readability
|
||||
payload bson.D
|
||||
user string
|
||||
|
||||
expected bson.D
|
||||
expectedMechanisms bson.A
|
||||
expectedCredentialsComparable bson.D // field keys without values are compared for `salt`, `serverKey` and `storedKey` fields
|
||||
err *mongo.CommandError
|
||||
altMessage string
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"Empty": {
|
||||
payload: bson.D{
|
||||
{"createUser", ""},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "",
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "User document needs 'user' field to be non-empty",
|
||||
},
|
||||
altMessage: "createUser cannot be empty",
|
||||
},
|
||||
"EmptyPassword": {
|
||||
payload: bson.D{
|
||||
{"createUser", "empty_password_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", ""},
|
||||
},
|
||||
user: "empty_password_user",
|
||||
err: &mongo.CommandError{
|
||||
Code: 50687,
|
||||
Name: "Location50687",
|
||||
Message: "Error preflighting UTF-8 conversion: U_STRING_NOT_TERMINATED_WARNING",
|
||||
},
|
||||
altMessage: "Password cannot be empty",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/934",
|
||||
},
|
||||
"BadPasswordValue": {
|
||||
payload: bson.D{
|
||||
{"createUser", "bad_password_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "pass\x00word"},
|
||||
},
|
||||
user: "bad_password_user",
|
||||
err: &mongo.CommandError{
|
||||
Code: 50692,
|
||||
Name: "Location50692",
|
||||
Message: "Error preflighting normalization: U_STRINGPREP_PROHIBITED_ERROR",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/941",
|
||||
},
|
||||
"BadPasswordType": {
|
||||
payload: bson.D{
|
||||
{"createUser", "bad_password_type_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", true},
|
||||
},
|
||||
user: "bad_password_type_user",
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'createUser.pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
altMessage: "BSON field 'pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
"AlreadyExists": {
|
||||
payload: bson.D{
|
||||
{"createUser", "should_already_exist"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "should_already_exist",
|
||||
err: &mongo.CommandError{
|
||||
Code: 51003,
|
||||
Name: "Location51003",
|
||||
Message: fmt.Sprintf("User \"should_already_exist@%s\" already exists", db.Name()),
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/934",
|
||||
},
|
||||
"EmptyMechanism": {
|
||||
payload: bson.D{
|
||||
{"createUser", "empty_mechanism_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
{"mechanisms", bson.A{}},
|
||||
},
|
||||
user: "empty_mechanism_user",
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "mechanisms field must not be empty",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/913",
|
||||
},
|
||||
"BadAuthMechanism": {
|
||||
payload: bson.D{
|
||||
{"createUser", "success_user_with_scram_sha_1"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
{"mechanisms", bson.A{"BAD"}},
|
||||
},
|
||||
user: "success_user_with_scram_sha_1",
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "Unknown auth mechanism 'BAD'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/913",
|
||||
},
|
||||
"MissingPwdOrExternal": {
|
||||
payload: bson.D{
|
||||
{"createUser", "mising_pwd_or_external"},
|
||||
{"roles", bson.A{}},
|
||||
},
|
||||
user: "mising_pwd_or_external",
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "Must provide a 'pwd' field for all user documents, except those with '$external' as the user's source db",
|
||||
},
|
||||
altMessage: "createUser, pwd and roles are required fields",
|
||||
},
|
||||
"Success": {
|
||||
payload: bson.D{
|
||||
{"createUser", "success_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "success_user",
|
||||
expectedMechanisms: bson.A{"SCRAM-SHA-256"},
|
||||
expected: bson.D{
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
expectedCredentialsComparable: bson.D{
|
||||
{"SCRAM-SHA-256", bson.D{
|
||||
{"iterationCount", int32(15000)},
|
||||
{Key: "salt"},
|
||||
{Key: "storedKey"},
|
||||
{Key: "serverKey"},
|
||||
}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"SuccessWithSCRAMSHA256": {
|
||||
payload: bson.D{
|
||||
{"createUser", "success_user_with_scram_sha_256"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
},
|
||||
user: "success_user_with_scram_sha_256",
|
||||
expectedMechanisms: bson.A{"SCRAM-SHA-256"},
|
||||
expected: bson.D{
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
expectedCredentialsComparable: bson.D{
|
||||
{"SCRAM-SHA-256", bson.D{
|
||||
{"iterationCount", int32(15000)},
|
||||
{Key: "salt"},
|
||||
{Key: "storedKey"},
|
||||
{Key: "serverKey"},
|
||||
}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"WithComment": {
|
||||
payload: bson.D{
|
||||
{"createUser", "with_comment_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
{"comment", "test string comment"},
|
||||
},
|
||||
user: "with_comment_user",
|
||||
expectedMechanisms: bson.A{"SCRAM-SHA-256"},
|
||||
expected: bson.D{
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
expectedCredentialsComparable: bson.D{
|
||||
{"SCRAM-SHA-256", bson.D{
|
||||
{"iterationCount", int32(15000)},
|
||||
{Key: "salt"},
|
||||
{Key: "storedKey"},
|
||||
{Key: "serverKey"},
|
||||
}},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"MissingRoles": {
|
||||
payload: bson.D{
|
||||
{"createUser", "missing_roles"},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "missing_roles",
|
||||
err: &mongo.CommandError{
|
||||
Code: 40414,
|
||||
Name: "Location40414",
|
||||
Message: "BSON field 'createUser.roles' is missing but a required field",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/934",
|
||||
},
|
||||
"NilRoles": {
|
||||
payload: bson.D{
|
||||
{"createUser", "nil_roles"},
|
||||
{"roles", nil},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "nil_roles",
|
||||
err: &mongo.CommandError{
|
||||
Code: 10065,
|
||||
Name: "Location10065",
|
||||
Message: "invalid parameter: expected an object (roles)",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/934",
|
||||
},
|
||||
"InvalidRoles": {
|
||||
payload: bson.D{
|
||||
{"createUser", "invalid_roles"},
|
||||
{"roles", "not-array"},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
user: "invalid_roles",
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'createUser.roles' is the wrong type 'string', expected type 'array'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/934",
|
||||
},
|
||||
}
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", "should_already_exist"}})
|
||||
|
||||
// The subtest "AlreadyExists" tries to create the following user, which should fail with an error that the user already exists.
|
||||
// Here, we create the user for the very first time to populate the database.
|
||||
err := db.RunCommand(ctx, bson.D{
|
||||
{"createUser", "should_already_exist"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
}).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
var t testing.TB = tt
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(t, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
if tc.user != "should_already_exist" {
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", tc.user}})
|
||||
}
|
||||
|
||||
var res bson.D
|
||||
err := db.RunCommand(ctx, tc.payload).Decode(&res)
|
||||
if tc.err != nil {
|
||||
integration.AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertEqualDocuments(t, tc.expected, res)
|
||||
|
||||
err = db.RunCommand(ctx, bson.D{{"usersInfo", tc.user}, {"showCredentials", true}}).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
|
||||
var resComparable bson.D
|
||||
|
||||
for _, v := range res {
|
||||
if v.Key != "users" {
|
||||
resComparable = append(resComparable, v)
|
||||
continue
|
||||
}
|
||||
|
||||
var userComparable bson.D
|
||||
|
||||
for _, u := range v.Value.(bson.A)[0].(bson.D) {
|
||||
switch u.Key {
|
||||
case "userId":
|
||||
uuid, ok := u.Value.(primitive.Binary)
|
||||
assert.True(t, ok, "userId is not a primitive.Binary")
|
||||
assert.Equal(t, bson.TypeBinaryUUID, uuid.Subtype, "uuid subtype")
|
||||
assert.Equal(t, 16, len(uuid.Data), "UUID length")
|
||||
userComparable = append(userComparable, bson.E{Key: u.Key})
|
||||
case "credentials":
|
||||
var credentials bson.D
|
||||
|
||||
for _, c := range u.Value.(bson.D) {
|
||||
if c.Key != "SCRAM-SHA-256" {
|
||||
continue
|
||||
}
|
||||
|
||||
var mechanism bson.D
|
||||
|
||||
for _, m := range c.Value.(bson.D) {
|
||||
switch m.Key {
|
||||
case "salt", "serverKey", "storedKey":
|
||||
assert.NotEmpty(t, m.Value.(string))
|
||||
mechanism = append(mechanism, bson.E{Key: m.Key})
|
||||
default:
|
||||
mechanism = append(mechanism, m)
|
||||
}
|
||||
}
|
||||
|
||||
credentials = append(credentials, bson.E{Key: c.Key, Value: mechanism})
|
||||
}
|
||||
|
||||
userComparable = append(userComparable, bson.E{Key: u.Key, Value: credentials})
|
||||
case "mechanisms":
|
||||
var mechanismsComparable bson.A
|
||||
|
||||
for _, m := range u.Value.(bson.A) {
|
||||
if m != "SCRAM-SHA-256" {
|
||||
continue
|
||||
}
|
||||
|
||||
mechanismsComparable = append(mechanismsComparable, m)
|
||||
}
|
||||
|
||||
userComparable = append(userComparable, bson.E{Key: u.Key, Value: mechanismsComparable})
|
||||
default:
|
||||
userComparable = append(userComparable, u)
|
||||
}
|
||||
}
|
||||
|
||||
resComparable = append(resComparable, bson.E{Key: v.Key, Value: bson.A{userComparable}})
|
||||
}
|
||||
|
||||
expectedUsersInfo := bson.D{
|
||||
{
|
||||
"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.%s", db.Name(), tc.user)},
|
||||
{Key: "userId"},
|
||||
{"user", tc.user},
|
||||
{"db", db.Name()},
|
||||
{"credentials", tc.expectedCredentialsComparable},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", tc.expectedMechanisms},
|
||||
},
|
||||
},
|
||||
},
|
||||
{"ok", float64(1)},
|
||||
}
|
||||
|
||||
integration.AssertEqualDocuments(t, expectedUsersInfo, resComparable)
|
||||
})
|
||||
}
|
||||
}
|
||||
+23
-26
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package users
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -24,26 +24,29 @@ import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/types"
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration"
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestDropAllUsersFromDatabase(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestDropAllUsersFromDatabaseCommand(tt *testing.T) {
|
||||
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/864")
|
||||
|
||||
s := setup.SetupWithOpts(t, nil)
|
||||
tt.Parallel()
|
||||
|
||||
s := setup.SetupWithOpts(tt, nil)
|
||||
ctx := s.Ctx
|
||||
db := s.Collection.Database()
|
||||
client := db.Client()
|
||||
|
||||
quantity := 5 // Add some users to the database.
|
||||
for i := 1; i <= quantity; i++ {
|
||||
username := fmt.Sprintf("user_%d", i)
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", username}})
|
||||
|
||||
err := db.RunCommand(ctx, bson.D{
|
||||
{"createUser", fmt.Sprintf("user_%d", i)},
|
||||
{"createUser", username},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
}).Err()
|
||||
@@ -61,7 +64,7 @@ func TestDropAllUsersFromDatabase(t *testing.T) {
|
||||
assertDropAllUsersFromDatabase(t, ctx, db, 0)
|
||||
}
|
||||
|
||||
func assertDropAllUsersFromDatabase(t *testing.T, ctx context.Context, db *mongo.Database, quantity int) {
|
||||
func assertDropAllUsersFromDatabase(t testing.TB, ctx context.Context, db *mongo.Database, quantity int) {
|
||||
t.Helper()
|
||||
|
||||
var res bson.D
|
||||
@@ -71,23 +74,17 @@ func assertDropAllUsersFromDatabase(t *testing.T, ctx context.Context, db *mongo
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := integration.ConvertDocument(t, res)
|
||||
actual.Remove("$clusterTime")
|
||||
actual.Remove("operationTime")
|
||||
|
||||
expected := must.NotFail(types.NewDocument("n", int32(quantity), "ok", float64(1)))
|
||||
testutil.AssertEqual(t, expected, actual)
|
||||
expected := bson.D{{"n", int32(quantity)}, {"ok", float64(1)}}
|
||||
integration.AssertEqualDocuments(t, expected, res)
|
||||
|
||||
var usersInfo bson.D
|
||||
err = db.RunCommand(ctx, bson.D{{"usersInfo", 1}}).Decode(&usersInfo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedUsersInfo := must.NotFail(types.NewDocument(
|
||||
"users", new(types.Array),
|
||||
"ok", float64(1),
|
||||
))
|
||||
actualUsersInfo := integration.ConvertDocument(t, usersInfo)
|
||||
actualUsersInfo.Remove("$clusterTime")
|
||||
actualUsersInfo.Remove("operationTime")
|
||||
testutil.AssertEqual(t, expectedUsersInfo, actualUsersInfo)
|
||||
expectedUsersInfo := bson.D{
|
||||
{"users", bson.A{}},
|
||||
{"ok", float64(1)},
|
||||
}
|
||||
|
||||
integration.AssertEqualDocuments(t, expectedUsersInfo, usersInfo)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestDropUserCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupWithOpts(t, nil)
|
||||
ctx, db := s.Ctx, s.Collection.Database()
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", "a_user"}})
|
||||
|
||||
errcmd := db.RunCommand(ctx, bson.D{ // avoid data race with and shadowing of err in parallel subtests below
|
||||
{"createUser", "a_user"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "password"},
|
||||
}).Err()
|
||||
require.NoError(t, errcmd)
|
||||
|
||||
testCases := map[string]struct { //nolint:vet // for readability
|
||||
username string
|
||||
|
||||
expected bson.D
|
||||
err *mongo.CommandError
|
||||
altMessage string
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NotFound": {
|
||||
username: "not_found_user",
|
||||
err: &mongo.CommandError{
|
||||
Code: 11,
|
||||
Name: "UserNotFound",
|
||||
Message: fmt.Sprintf("User 'not_found_user@%s' not found", db.Name()),
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/942",
|
||||
},
|
||||
"Success": {
|
||||
username: "a_user",
|
||||
expected: bson.D{
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
var t testing.TB = tt
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(t, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
var res bson.D
|
||||
err := db.RunCommand(ctx, bson.D{{"dropUser", tc.username}}).Decode(&res)
|
||||
if tc.err != nil {
|
||||
integration.AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
integration.AssertEqualDocuments(t, tc.expected, res)
|
||||
|
||||
err = db.RunCommand(ctx, bson.D{{"usersInfo", bson.A{
|
||||
bson.D{
|
||||
{"user", tc.username},
|
||||
{"db", db.Name()},
|
||||
},
|
||||
}}}).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := bson.D{
|
||||
{"users", bson.A{}},
|
||||
{"ok", float64(1)},
|
||||
}
|
||||
integration.AssertEqualDocuments(t, expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -12,15 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
// Package auth contains tests for authentication and user management commands:
|
||||
// - createUser;
|
||||
// - dropAllUsersFromDatabase;
|
||||
// - dropUser;
|
||||
// - logout;
|
||||
// - updateUser;
|
||||
// - usersInfo.
|
||||
package auth
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// Placeholder stores the number of the relevant placeholder of the query.
|
||||
type Placeholder int
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
// Next increases the identifier value for the next variable in the PostgreSQL query.
|
||||
func (p *Placeholder) Next() string {
|
||||
*p++
|
||||
return "$" + strconv.Itoa(int(*p))
|
||||
func TestMain(m *testing.M) {
|
||||
setup.Main(m)
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestUpdateUserCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupWithOpts(t, nil)
|
||||
ctx, db := s.Ctx, s.Collection.Database()
|
||||
|
||||
testCases := map[string]struct { //nolint:vet // for readability
|
||||
username string
|
||||
password string
|
||||
mechanisms bson.A
|
||||
updatePayload bson.D
|
||||
|
||||
expected bson.D
|
||||
err *mongo.CommandError
|
||||
altMessage string
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"MissingFields": {
|
||||
username: "missing_fields",
|
||||
password: "pass123654",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "missing_fields"},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "Must specify at least one field to update in updateUser",
|
||||
},
|
||||
altMessage: "updateUser and pwd are required fields",
|
||||
},
|
||||
"UserNotFound": {
|
||||
username: "do_not_use",
|
||||
password: "pass123654",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "not_found"},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 11,
|
||||
Name: "UserNotFound",
|
||||
Message: fmt.Sprintf("User not_found@%s not found", db.Name()),
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/945",
|
||||
},
|
||||
"EmptyUsername": {
|
||||
username: "not_empty_username",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", ""},
|
||||
{"pwd", "anewpassword"},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 11,
|
||||
Name: "UserNotFound",
|
||||
Message: fmt.Sprintf("User @%s not found", db.Name()),
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/945",
|
||||
},
|
||||
"EmptyPassword": {
|
||||
username: "a_user_bad_password",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user_bad_password"},
|
||||
{"pwd", ""},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 50687,
|
||||
Name: "Location50687",
|
||||
Message: "Error preflighting UTF-8 conversion: U_STRING_NOT_TERMINATED_WARNING",
|
||||
},
|
||||
altMessage: "Password cannot be empty",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/945",
|
||||
},
|
||||
"BadPasswordValue": {
|
||||
username: "b_user_bad_password_value",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "b_user_bad_password_value"},
|
||||
{"pwd", true},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'updateUser.pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
altMessage: "BSON field 'pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
"BadPasswordType": {
|
||||
username: "a_user_bad_password_type",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user_bad_password_type"},
|
||||
{"pwd", true},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'updateUser.pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
altMessage: "BSON field 'pwd' is the wrong type 'bool', expected type 'string'",
|
||||
},
|
||||
"SamePassword": {
|
||||
username: "same_password_user",
|
||||
password: "donotchange",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "same_password_user"},
|
||||
{"pwd", "donotchange"},
|
||||
},
|
||||
expected: bson.D{
|
||||
{"users", bson.A{bson.D{
|
||||
{"_id", fmt.Sprintf("%s.same_password_user", db.Name())},
|
||||
{"user", "same_password_user"},
|
||||
{"db", db.Name()},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
}}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"PasswordChange": {
|
||||
username: "a_user",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user"},
|
||||
{"pwd", "anewpassword"},
|
||||
},
|
||||
expected: bson.D{
|
||||
{"users", bson.A{bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a_user", db.Name())},
|
||||
{"user", "a_user"},
|
||||
{"db", db.Name()},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
}}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"PasswordChangeWithSCRAMMechanism": {
|
||||
username: "a_user_with_scram_mechanism",
|
||||
password: "password",
|
||||
mechanisms: bson.A{"SCRAM-SHA-256"},
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user_with_scram_mechanism"},
|
||||
{"pwd", "anewpassword"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
},
|
||||
expected: bson.D{
|
||||
{"users", bson.A{bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a_user_with_scram_mechanism", db.Name())},
|
||||
{"user", "a_user_with_scram_mechanism"},
|
||||
{"db", db.Name()},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
}}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/946",
|
||||
},
|
||||
"PasswordChangeWithBadAuthMechanism": {
|
||||
username: "a_user_with_mechanism_bad",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user_with_mechanism_bad"},
|
||||
{"pwd", "anewpassword"},
|
||||
{"mechanisms", bson.A{"BAD"}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Name: "BadValue",
|
||||
Message: "Unknown auth mechanism 'BAD'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/946",
|
||||
},
|
||||
"PasswordChangeWithRoles": {
|
||||
username: "a_user_with_no_roles",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "a_user_with_no_roles"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "anewpassword"},
|
||||
},
|
||||
expected: bson.D{
|
||||
{"users", bson.A{bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a_user_with_no_roles", db.Name())},
|
||||
{"user", "a_user_with_no_roles"},
|
||||
{"db", db.Name()},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
}}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/944",
|
||||
},
|
||||
"WithComment": {
|
||||
username: "another_user",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "another_user"},
|
||||
{"pwd", "anewpassword"},
|
||||
{"comment", "test string comment"},
|
||||
},
|
||||
expected: bson.D{
|
||||
{"users", bson.A{bson.D{
|
||||
{"_id", fmt.Sprintf("%s.another_user", db.Name())},
|
||||
{"user", "another_user"},
|
||||
{"db", db.Name()},
|
||||
{"roles", bson.A{}},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
}}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"InvalidRoles": {
|
||||
username: "invalid_roles",
|
||||
password: "password",
|
||||
updatePayload: bson.D{
|
||||
{"updateUser", "invalid_roles"},
|
||||
{"roles", "not-array"},
|
||||
{"pwd", "password"},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 14,
|
||||
Name: "TypeMismatch",
|
||||
Message: "BSON field 'updateUser.roles' is the wrong type 'string', expected type 'array'",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/944",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
var t testing.TB = tt
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(t, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", tc.username}})
|
||||
|
||||
createPayload := bson.D{
|
||||
{"createUser", tc.username},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", tc.password},
|
||||
}
|
||||
|
||||
if tc.mechanisms != nil {
|
||||
createPayload = append(createPayload, bson.E{Key: "mechanisms", Value: tc.mechanisms})
|
||||
}
|
||||
|
||||
err := db.RunCommand(ctx, createPayload).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
var res bson.D
|
||||
err = db.RunCommand(ctx, tc.updatePayload).Decode(&res)
|
||||
if tc.err != nil {
|
||||
integration.AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
integration.AssertEqualDocuments(t, bson.D{{"ok", float64(1)}}, res)
|
||||
|
||||
err = db.RunCommand(ctx, bson.D{{"usersInfo", tc.username}}).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, v := range res {
|
||||
if v.Key != "users" {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredUser bson.D
|
||||
|
||||
for _, u := range v.Value.(bson.A)[0].(bson.D) {
|
||||
switch u.Key {
|
||||
case "userId":
|
||||
uuid, ok := u.Value.(primitive.Binary)
|
||||
assert.True(t, ok, "userId is not a primitive.Binary")
|
||||
assert.Equal(t, bson.TypeBinaryUUID, uuid.Subtype, "uuid subtype")
|
||||
assert.Equal(t, 16, len(uuid.Data), "UUID length")
|
||||
|
||||
case "mechanisms":
|
||||
var mechanismsComparable bson.A
|
||||
|
||||
for _, m := range u.Value.(bson.A) {
|
||||
if m != "SCRAM-SHA-256" {
|
||||
continue
|
||||
}
|
||||
|
||||
mechanismsComparable = append(mechanismsComparable, m)
|
||||
}
|
||||
|
||||
filteredUser = append(filteredUser, bson.E{Key: u.Key, Value: mechanismsComparable})
|
||||
default:
|
||||
filteredUser = append(filteredUser, u)
|
||||
}
|
||||
}
|
||||
|
||||
res[i].Value = bson.A{filteredUser}
|
||||
}
|
||||
|
||||
integration.AssertEqualDocuments(t, tc.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,743 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
|
||||
|
||||
"github.com/FerretDB/FerretDB/v2/integration"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
// createUser creates a bson.D command payload to create an user with the given username and password.
|
||||
func createUser(username, password string) bson.D {
|
||||
return bson.D{
|
||||
{"createUser", username},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", password},
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersInfoCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupWithOpts(t, nil)
|
||||
ctx, collection := s.Ctx, s.Collection
|
||||
client := collection.Database().Client()
|
||||
|
||||
dbPrefix := testutil.DatabaseName(t)
|
||||
|
||||
dbNames := struct {
|
||||
A string
|
||||
B string
|
||||
C string
|
||||
D string
|
||||
E string
|
||||
}{
|
||||
A: dbPrefix,
|
||||
B: dbPrefix + "_example",
|
||||
C: dbPrefix + "_few",
|
||||
D: dbPrefix + "_another",
|
||||
E: dbPrefix + "allbackends",
|
||||
}
|
||||
|
||||
dbToUsers := []struct {
|
||||
dbName string
|
||||
payloads []bson.D
|
||||
}{
|
||||
{
|
||||
dbName: dbNames.A,
|
||||
payloads: []bson.D{
|
||||
createUser("one", "pwd1"),
|
||||
createUser("two", "pwd2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
dbName: dbNames.B,
|
||||
payloads: []bson.D{
|
||||
createUser("a", "password1"),
|
||||
createUser("b", "password2"),
|
||||
createUser("c", "password3"),
|
||||
},
|
||||
},
|
||||
{
|
||||
dbName: dbNames.C,
|
||||
payloads: []bson.D{
|
||||
createUser("i", "password1"),
|
||||
createUser("j", "password2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
dbName: dbNames.D,
|
||||
payloads: []bson.D{
|
||||
createUser("singleuser", "123456"),
|
||||
},
|
||||
},
|
||||
{
|
||||
dbName: dbNames.E,
|
||||
payloads: []bson.D{
|
||||
{
|
||||
{"createUser", "WithSCRAMSHA256"},
|
||||
{"roles", bson.A{}},
|
||||
{"pwd", "pwd1"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
testUsernames := map[string]string{
|
||||
"one": dbNames.A,
|
||||
"two": dbNames.A,
|
||||
"a": dbNames.B,
|
||||
"b": dbNames.B,
|
||||
"c": dbNames.B,
|
||||
"i": dbNames.C,
|
||||
"j": dbNames.C,
|
||||
"singleuser": dbNames.D,
|
||||
"WithSCRAMSHA256": dbNames.E,
|
||||
}
|
||||
|
||||
for username, dbName := range testUsernames {
|
||||
db := client.Database(dbName)
|
||||
_ = db.RunCommand(ctx, bson.D{{"dropUser", username}})
|
||||
}
|
||||
|
||||
for _, inserted := range dbToUsers {
|
||||
db := client.Database(inserted.dbName)
|
||||
|
||||
t.Cleanup(func() {
|
||||
db.RunCommand(ctx, bson.D{{"dropAllUsersFromDatabase", 1}})
|
||||
})
|
||||
|
||||
for _, payload := range inserted.payloads {
|
||||
err := db.RunCommand(ctx, payload).Err()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
testCases := map[string]struct { //nolint:vet // for readability
|
||||
dbName string
|
||||
payload bson.D
|
||||
|
||||
expectedComparable bson.D // field keys without values are compared for `userId`, `salt`, `serverKey` and `storedKey` fields
|
||||
err *mongo.CommandError
|
||||
altMessage string
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"NoUserFound": {
|
||||
dbName: "no_users",
|
||||
payload: bson.D{
|
||||
{"usersInfo", int64(1)},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"Nil": {
|
||||
dbName: dbNames.A,
|
||||
payload: bson.D{
|
||||
{"usersInfo", nil},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must be either a string or an object",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: "Unusupported value for usersInfo",
|
||||
},
|
||||
"UnknownField": {
|
||||
dbName: dbNames.A,
|
||||
payload: bson.D{
|
||||
// Note: if user is passed here, this test will fail only on MongoDB.
|
||||
{"usersInfo", bson.D{{"foo", "bar"}}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName contains an unknown field named: 'foo",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: "UserName must contain a field named: user",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"Default": {
|
||||
dbName: dbNames.A,
|
||||
payload: bson.D{
|
||||
{"usersInfo", "one"},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.one", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/961
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/962
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"WithSCRAMSHA256": {
|
||||
dbName: dbNames.E,
|
||||
payload: bson.D{
|
||||
{"usersInfo", "WithSCRAMSHA256"},
|
||||
{"showCredentials", true},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.WithSCRAMSHA256", dbNames.E)},
|
||||
{Key: "userId"},
|
||||
{"user", "WithSCRAMSHA256"},
|
||||
{"db", dbNames.E},
|
||||
{"credentials", bson.D{
|
||||
{"SCRAM-SHA-256", bson.D{
|
||||
{"iterationCount", int32(15000)},
|
||||
{Key: "salt"},
|
||||
{Key: "storedKey"},
|
||||
{Key: "serverKey"},
|
||||
}},
|
||||
}},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"FromSameDatabase": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.A{
|
||||
"a", "b",
|
||||
},
|
||||
}},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "a"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.b", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "b"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"FromSameDatabaseWithMissingUser": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.A{
|
||||
"a", "b", "missing",
|
||||
},
|
||||
}},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "a"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.b", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "b"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"FromAnotherDatabase": {
|
||||
dbName: dbNames.D,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.D{
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
},
|
||||
}},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.one", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/864",
|
||||
},
|
||||
"BadType": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{{
|
||||
"usersInfo", true,
|
||||
}},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must be either a string or an object",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: "Unusupported value for usersInfo",
|
||||
},
|
||||
"BadTypeUsername": {
|
||||
dbName: dbNames.D,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.D{
|
||||
{"user", 123},
|
||||
{"db", dbNames.B},
|
||||
},
|
||||
}},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must contain a string field named: user. But, has type int",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: fmt.Sprintf("Unsupported value specified for db : %s", dbNames.B),
|
||||
},
|
||||
"BadTypeDB": {
|
||||
dbName: dbNames.D,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.D{
|
||||
{"user", "one"},
|
||||
{"db", 123},
|
||||
},
|
||||
}},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must contain a string field named: db. But, has type int",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: "UserName must contain a string field named: db. But, has type int32",
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/964",
|
||||
},
|
||||
"FromOthersMultipleDatabases": {
|
||||
dbName: dbNames.C,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.A{
|
||||
bson.D{
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
},
|
||||
bson.D{
|
||||
{"user", "i"},
|
||||
{"db", dbNames.C},
|
||||
},
|
||||
},
|
||||
}},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.i", dbNames.C)},
|
||||
{Key: "userId"},
|
||||
{"user", "i"},
|
||||
{"db", dbNames.C},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.one", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"FromMixed": {
|
||||
dbName: dbNames.C,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.A{
|
||||
bson.D{
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
},
|
||||
"i",
|
||||
},
|
||||
}},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.i", dbNames.C)},
|
||||
{Key: "userId"},
|
||||
{"user", "i"},
|
||||
{"db", dbNames.C},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.one", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"SingleDatabaseInt": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{
|
||||
{"usersInfo", int32(1)},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "a"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.b", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "b"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.c", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "c"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/939",
|
||||
},
|
||||
"SingleDatabaseLong": {
|
||||
dbName: "no_users",
|
||||
payload: bson.D{
|
||||
{"usersInfo", int64(1)},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"SingleDatabaseFloat": {
|
||||
dbName: "no_users",
|
||||
payload: bson.D{
|
||||
{"usersInfo", float64(1)},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"Multiple": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{
|
||||
{"usersInfo", bson.A{
|
||||
bson.D{{"user", "b"}, {"db", dbNames.B}},
|
||||
bson.D{{"user", "c"}, {"db", dbNames.B}},
|
||||
}},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.b", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "b"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.c", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "c"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"EmptyUsers": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{{
|
||||
"usersInfo", bson.A{},
|
||||
}},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "$and/$or/$nor must be a nonempty array",
|
||||
Name: "BadValue",
|
||||
},
|
||||
altMessage: "Unusupported value for usersInfo",
|
||||
},
|
||||
"MissingDBFieldName": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{
|
||||
{"usersInfo", bson.A{
|
||||
bson.D{{"user", "missing_db"}},
|
||||
}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must contain a field named: db",
|
||||
Name: "BadValue",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"MissingUserFieldName": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{
|
||||
{"usersInfo", bson.A{
|
||||
bson.D{{"db", dbNames.B}},
|
||||
}},
|
||||
},
|
||||
err: &mongo.CommandError{
|
||||
Code: 2,
|
||||
Message: "UserName must contain a field named: user",
|
||||
Name: "BadValue",
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
"ForAllDBs": {
|
||||
dbName: dbNames.B,
|
||||
payload: bson.D{
|
||||
{"usersInfo", bson.D{
|
||||
{"forAllDBs", true},
|
||||
}},
|
||||
},
|
||||
expectedComparable: bson.D{
|
||||
{"users", bson.A{
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.WithSCRAMSHA256", dbNames.E)},
|
||||
{Key: "userId"},
|
||||
{"user", "WithSCRAMSHA256"},
|
||||
{"db", dbNames.E},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.a", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "a"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.b", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "b"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.c", dbNames.B)},
|
||||
{Key: "userId"},
|
||||
{"user", "c"},
|
||||
{"db", dbNames.B},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.i", dbNames.C)},
|
||||
{Key: "userId"},
|
||||
{"user", "i"},
|
||||
{"db", dbNames.C},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.j", dbNames.C)},
|
||||
{Key: "userId"},
|
||||
{"user", "j"},
|
||||
{"db", dbNames.C},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.one", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "one"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.singleuser", dbNames.D)},
|
||||
{Key: "userId"},
|
||||
{"user", "singleuser"},
|
||||
{"db", dbNames.D},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
bson.D{
|
||||
{"_id", fmt.Sprintf("%s.two", dbNames.A)},
|
||||
{Key: "userId"},
|
||||
{"user", "two"},
|
||||
{"db", dbNames.A},
|
||||
{Key: "roles"},
|
||||
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
|
||||
},
|
||||
}},
|
||||
{"ok", float64(1)},
|
||||
},
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/963",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
var t testing.TB = tt
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
tt.Parallel()
|
||||
|
||||
var res bson.D
|
||||
err := client.Database(tc.dbName).RunCommand(ctx, tc.payload).Decode(&res)
|
||||
if tc.err != nil {
|
||||
integration.AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
var resComparable bson.D
|
||||
|
||||
for _, v := range res {
|
||||
if v.Key != "users" {
|
||||
resComparable = append(resComparable, v)
|
||||
continue
|
||||
}
|
||||
|
||||
var usersComparable bson.A
|
||||
|
||||
for _, u := range v.Value.(bson.A) {
|
||||
var userComparable bson.D
|
||||
|
||||
var userCreatedByOtherTests bool
|
||||
|
||||
for _, field := range u.(bson.D) {
|
||||
switch field.Key {
|
||||
case "userId":
|
||||
uuid, ok := field.Value.(primitive.Binary)
|
||||
assert.True(t, ok, "userId is not a primitive.Binary")
|
||||
assert.Equal(t, bson.TypeBinaryUUID, uuid.Subtype, "uuid subtype")
|
||||
assert.Equal(t, 16, len(uuid.Data), "UUID length")
|
||||
userComparable = append(userComparable, bson.E{Key: field.Key})
|
||||
case "credentials":
|
||||
var credentials bson.D
|
||||
|
||||
for _, c := range field.Value.(bson.D) {
|
||||
var mechanism bson.D
|
||||
|
||||
for _, m := range c.Value.(bson.D) {
|
||||
switch m.Key {
|
||||
case "salt", "serverKey", "storedKey":
|
||||
assert.NotEmpty(t, m.Value.(string))
|
||||
mechanism = append(mechanism, bson.E{Key: m.Key})
|
||||
default:
|
||||
mechanism = append(mechanism, m)
|
||||
}
|
||||
}
|
||||
|
||||
credentials = append(credentials, bson.E{Key: c.Key, Value: mechanism})
|
||||
}
|
||||
|
||||
userComparable = append(userComparable, bson.E{Key: field.Key, Value: credentials})
|
||||
case "user":
|
||||
// use username to check if user was created by this test
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/864
|
||||
_, ok := testUsernames[field.Value.(string)]
|
||||
if !ok {
|
||||
userCreatedByOtherTests = true
|
||||
}
|
||||
|
||||
userComparable = append(userComparable, field)
|
||||
case "roles":
|
||||
userComparable = append(userComparable, bson.E{Key: field.Key})
|
||||
default:
|
||||
userComparable = append(userComparable, field)
|
||||
}
|
||||
}
|
||||
|
||||
if userCreatedByOtherTests {
|
||||
continue
|
||||
}
|
||||
|
||||
usersComparable = append(usersComparable, userComparable)
|
||||
}
|
||||
|
||||
resComparable = append(resComparable, bson.E{Key: v.Key, Value: usersComparable})
|
||||
}
|
||||
|
||||
integration.AssertEqualDocuments(t, tc.expectedComparable, resComparable)
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+235
-103
@@ -27,16 +27,18 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/observability"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/observability"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/testutil"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func TestMostCommandsAreCaseSensitive(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, collection := setup.Setup(t)
|
||||
func TestCommandCaseSensitive(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
t := setup.FailsForFerretDB(tt, "https://github.com/FerretDB/FerretDB-DocumentDB/issues/9")
|
||||
ctx, collection := setup.Setup(tt)
|
||||
|
||||
db := collection.Database()
|
||||
|
||||
@@ -75,7 +77,7 @@ func TestFindNothing(t *testing.T) {
|
||||
|
||||
// FindOne sets limit parameter to 1, Find leaves it unset.
|
||||
err := collection.FindOne(ctx, bson.D{}).Decode(&doc)
|
||||
require.Equal(t, mongo.ErrNoDocuments, err)
|
||||
require.Equal(t, mongo.ErrNoDocuments, err, "actual: %s", err)
|
||||
assert.Equal(t, bson.D(nil), doc)
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ func TestInsertFind(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOtelComment(t *testing.T) {
|
||||
func TestFindOtelComment(t *testing.T) {
|
||||
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
||||
|
||||
ctx, span := otel.Tracer("").Start(ctx, "TestOtelComment")
|
||||
@@ -175,6 +177,7 @@ func TestUpdateCommentMethod(t *testing.T) {
|
||||
|
||||
func TestUpdateCommentQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
||||
|
||||
name := collection.Database().Name()
|
||||
@@ -220,6 +223,7 @@ func TestDeleteCommentMethod(t *testing.T) {
|
||||
|
||||
func TestDeleteCommentQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
||||
|
||||
name := collection.Database().Name()
|
||||
@@ -239,7 +243,7 @@ func TestDeleteCommentQuery(t *testing.T) {
|
||||
assert.Equal(t, expected, res)
|
||||
}
|
||||
|
||||
func TestEmptyKey(t *testing.T) {
|
||||
func TestFindEmptyKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
@@ -259,7 +263,7 @@ func TestEmptyKey(t *testing.T) {
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestCollectionName(t *testing.T) {
|
||||
func TestCreateCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
@@ -267,12 +271,19 @@ func TestCollectionName(t *testing.T) {
|
||||
collectionName300 := strings.Repeat("aB", 150)
|
||||
collectionName235 := strings.Repeat("a", 235)
|
||||
|
||||
cases := map[string]struct {
|
||||
// use short database name to stay within 255 bytes namespace limit for using long collection name
|
||||
dbName := "short-db"
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, collection.Database().Client().Database(dbName).Drop(ctx))
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
collection string // collection name, defaults to empty string
|
||||
|
||||
err *mongo.CommandError // optional, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // optional, skip test with a specified reason
|
||||
err *mongo.CommandError // optional, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
failsForFerretDB string
|
||||
}{
|
||||
"TooLongForBothDBs": {
|
||||
collection: collectionName300,
|
||||
@@ -280,14 +291,16 @@ func TestCollectionName(t *testing.T) {
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: fmt.Sprintf(
|
||||
"Fully qualified namespace is too long. Namespace: TestCollectionName.%s Max: 255",
|
||||
"Fully qualified namespace is too long. Namespace: %s.%s Max: 255",
|
||||
dbName,
|
||||
collectionName300,
|
||||
),
|
||||
},
|
||||
altMessage: fmt.Sprintf("Invalid collection name: %s", collectionName300),
|
||||
},
|
||||
"LongEnough": {
|
||||
collection: collectionName235,
|
||||
collection: collectionName235,
|
||||
failsForFerretDB: "https://github.com/FerretDB/FerretDB-DocumentDB/issues/380",
|
||||
},
|
||||
"Short": {
|
||||
collection: "a",
|
||||
@@ -311,7 +324,7 @@ func TestCollectionName(t *testing.T) {
|
||||
err: &mongo.CommandError{
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: "Invalid namespace specified 'TestCollectionName.'",
|
||||
Message: fmt.Sprintf("Invalid namespace specified '%s.'", dbName),
|
||||
},
|
||||
altMessage: "Invalid collection name: ",
|
||||
},
|
||||
@@ -351,21 +364,20 @@ func TestCollectionName(t *testing.T) {
|
||||
"Capital": {
|
||||
collection: "A",
|
||||
},
|
||||
"Sqlite": {
|
||||
collection: "sqlite_",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
name, tc := name, tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
tt.Parallel()
|
||||
|
||||
var t testing.TB = tt
|
||||
if tc.failsForFerretDB != "" {
|
||||
t = setup.FailsForFerretDB(tt, tc.failsForFerretDB)
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
db := collection.Database().Client().Database(dbName)
|
||||
|
||||
err := collection.Database().CreateCollection(ctx, tc.collection)
|
||||
err := db.CreateCollection(ctx, tc.collection)
|
||||
if tc.err != nil {
|
||||
AssertEqualAltCommandError(t, *tc.err, tc.altMessage, err)
|
||||
return
|
||||
@@ -373,11 +385,11 @@ func TestCollectionName(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
names, err := collection.Database().ListCollectionNames(ctx, bson.D{})
|
||||
names, err := db.ListCollectionNames(ctx, bson.D{})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, names, tc.collection)
|
||||
|
||||
newCollection := collection.Database().Collection(tc.collection)
|
||||
newCollection := db.Collection(tc.collection)
|
||||
|
||||
doc := bson.D{{"_id", "item"}}
|
||||
_, err = newCollection.InsertOne(ctx, doc)
|
||||
@@ -389,14 +401,13 @@ func TestCollectionName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseName(t *testing.T) {
|
||||
func TestCreateCollectionDatabaseName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("NoErr", func(t *testing.T) {
|
||||
ctx, collection := setup.Setup(t)
|
||||
for name, tc := range map[string]struct {
|
||||
db string // database name, defaults to empty string
|
||||
skip string // optional, skip test with a specified reason
|
||||
db string // database name, defaults to empty string
|
||||
}{
|
||||
"Dash": {
|
||||
db: "--",
|
||||
@@ -404,9 +415,6 @@ func TestDatabaseName(t *testing.T) {
|
||||
"Underscore": {
|
||||
db: "__",
|
||||
},
|
||||
"Sqlite": {
|
||||
db: "sqlite_",
|
||||
},
|
||||
"Number": {
|
||||
db: "0prefix",
|
||||
},
|
||||
@@ -414,12 +422,7 @@ func TestDatabaseName(t *testing.T) {
|
||||
db: strings.Repeat("a", 63),
|
||||
},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
// there is no explicit command to create database, so create collection instead
|
||||
@@ -437,12 +440,11 @@ func TestDatabaseName(t *testing.T) {
|
||||
|
||||
dbName64 := strings.Repeat("a", 64)
|
||||
|
||||
cases := map[string]struct {
|
||||
testCases := map[string]struct {
|
||||
db string // database name, defaults to empty string
|
||||
|
||||
err *mongo.CommandError // required, expected error from MongoDB
|
||||
altMessage string // optional, alternative error message for FerretDB, ignored if empty
|
||||
skip string // optional, skip test with a specified reason
|
||||
}{
|
||||
"TooLongForBothDBs": {
|
||||
db: dbName64,
|
||||
@@ -451,15 +453,16 @@ func TestDatabaseName(t *testing.T) {
|
||||
Code: 73,
|
||||
Message: "db name must be at most 63 characters, found: 64",
|
||||
},
|
||||
altMessage: fmt.Sprintf("Invalid namespace specified '%s.TestDatabaseName-Err'", dbName64),
|
||||
altMessage: "database name is too long",
|
||||
},
|
||||
"WithASlash": {
|
||||
db: "/",
|
||||
err: &mongo.CommandError{
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: `Invalid namespace specified '/.TestDatabaseName-Err'`,
|
||||
Message: `Invalid namespace specified '/.TestCreateCollectionDatabaseName-Err'`,
|
||||
},
|
||||
altMessage: "Database / has an invalid character /",
|
||||
},
|
||||
|
||||
"WithABackslash": {
|
||||
@@ -467,25 +470,27 @@ func TestDatabaseName(t *testing.T) {
|
||||
err: &mongo.CommandError{
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: `Invalid namespace specified '\.TestDatabaseName-Err'`,
|
||||
Message: `Invalid namespace specified '\.TestCreateCollectionDatabaseName-Err'`,
|
||||
},
|
||||
altMessage: `Database \ has an invalid character \`,
|
||||
},
|
||||
"WithADollarSign": {
|
||||
db: "name_with_a-$",
|
||||
err: &mongo.CommandError{
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: `Invalid namespace: name_with_a-$.TestDatabaseName-Err`,
|
||||
Message: `Invalid namespace: name_with_a-$.TestCreateCollectionDatabaseName-Err`,
|
||||
},
|
||||
altMessage: `Invalid namespace specified 'name_with_a-$.TestDatabaseName-Err'`,
|
||||
altMessage: "Database name_with_a-$ has an invalid character $",
|
||||
},
|
||||
"WithSpace": {
|
||||
db: "data base",
|
||||
err: &mongo.CommandError{
|
||||
Name: "InvalidNamespace",
|
||||
Code: 73,
|
||||
Message: `Invalid namespace specified 'data base.TestDatabaseName-Err'`,
|
||||
Message: `Invalid namespace specified 'data base.TestCreateCollectionDatabaseName-Err'`,
|
||||
},
|
||||
altMessage: "Database data base has an invalid character ",
|
||||
},
|
||||
"WithDot": {
|
||||
db: "database.test",
|
||||
@@ -494,17 +499,12 @@ func TestDatabaseName(t *testing.T) {
|
||||
Code: 73,
|
||||
Message: `'.' is an invalid character in a db name: database.test`,
|
||||
},
|
||||
altMessage: `Invalid namespace specified 'database.test.TestDatabaseName-Err'`,
|
||||
altMessage: "Database database.test has an invalid character .",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
name, tc := name, tc
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip != "" {
|
||||
t.Skip(tc.skip)
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
require.NotNil(t, tc.err, "err must not be nil")
|
||||
@@ -517,28 +517,19 @@ func TestDatabaseName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDebugError(t *testing.T) {
|
||||
func TestDebugCommandErrors(t *testing.T) {
|
||||
setup.SkipForMongoDB(t, "FerretDB-specific command")
|
||||
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
db := collection.Database()
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/2412
|
||||
t.Run("ValidationError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
db := collection.Database()
|
||||
|
||||
err := db.RunCommand(ctx, bson.D{{"debugError", bson.D{{"NaN", math.NaN()}}}}).Err()
|
||||
require.ErrorContains(t, err, "socket was unexpectedly closed")
|
||||
})
|
||||
|
||||
t.Run("LazyError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
db := collection.Database()
|
||||
|
||||
err := db.RunCommand(ctx, bson.D{{"debugError", "lazy error"}}).Err()
|
||||
expected := mongo.CommandError{
|
||||
Code: 1,
|
||||
@@ -553,9 +544,6 @@ func TestDebugError(t *testing.T) {
|
||||
t.Run("OtherError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
db := collection.Database()
|
||||
|
||||
err := db.RunCommand(ctx, bson.D{{"debugError", "other error"}}).Err()
|
||||
expected := mongo.CommandError{
|
||||
Code: 1,
|
||||
@@ -574,23 +562,15 @@ func TestPingCommand(t *testing.T) {
|
||||
ctx, collection := setup.Setup(t)
|
||||
db := collection.Database()
|
||||
|
||||
expected := ConvertDocument(t, bson.D{{"ok", float64(1)}})
|
||||
|
||||
t.Run("Multiple", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
res := db.RunCommand(ctx, bson.D{{"ping", int32(1)}})
|
||||
|
||||
var actualRes bson.D
|
||||
err := res.Decode(&actualRes)
|
||||
var res bson.D
|
||||
err := db.RunCommand(ctx, bson.D{{"ping", int32(1)}}).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := ConvertDocument(t, actualRes)
|
||||
actual.Remove("$clusterTime")
|
||||
actual.Remove("operationTime")
|
||||
|
||||
testutil.AssertEqual(t, expected, actual)
|
||||
AssertEqualDocuments(t, bson.D{{"ok", float64(1)}}, res)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -599,33 +579,29 @@ func TestPingCommand(t *testing.T) {
|
||||
|
||||
dbName := "NonExistentDatabase"
|
||||
|
||||
expectedDatabases, err := db.Client().ListDatabases(ctx, bson.D{{"name", dbName}})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, expectedDatabases.Databases)
|
||||
|
||||
res := db.Client().Database(dbName).RunCommand(ctx, bson.D{{"ping", int32(1)}})
|
||||
|
||||
var actualRes bson.D
|
||||
err = res.Decode(&actualRes)
|
||||
list, err := db.Client().ListDatabases(ctx, bson.D{{"name", dbName}})
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := ConvertDocument(t, actualRes)
|
||||
actual.Remove("$clusterTime")
|
||||
actual.Remove("operationTime")
|
||||
for _, dbSpec := range list.Databases {
|
||||
require.NotEqual(t, dbSpec.Name, dbName)
|
||||
}
|
||||
|
||||
expected := ConvertDocument(t, bson.D{{"ok", float64(1)}})
|
||||
|
||||
testutil.AssertEqual(t, expected, actual)
|
||||
|
||||
// Ensure that we don't create database on ping
|
||||
// This also means that no collection is created during ping.
|
||||
actualDatabases, err := db.Client().ListDatabases(ctx, bson.D{{"name", dbName}})
|
||||
var res bson.D
|
||||
err = db.Client().Database(dbName).RunCommand(ctx, bson.D{{"ping", int32(1)}}).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, actualDatabases.Databases)
|
||||
|
||||
AssertEqualDocuments(t, bson.D{{"ok", float64(1)}}, res)
|
||||
|
||||
list, err = db.Client().ListDatabases(ctx, bson.D{{"name", dbName}})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, dbSpec := range list.Databases {
|
||||
require.NotEqual(t, dbSpec.Name, dbName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMutatingClientMetadata(t *testing.T) {
|
||||
func TestHelloIsMasterCommandMutatingClientMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
@@ -684,3 +660,159 @@ func TestMutatingClientMetadata(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertNullStrings(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
_, err := collection.InsertOne(ctx, bson.D{
|
||||
{"_id", "document"},
|
||||
{"a", string([]byte{0})},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInsertUpdateNestedArrays(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
||||
|
||||
t.Run("Insert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := collection.InsertOne(ctx, bson.D{{"foo", bson.A{bson.A{"bar"}}}})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := collection.UpdateOne(ctx, bson.D{}, bson.D{{"$set", bson.D{{"foo", bson.A{bson.A{"bar"}}}}}})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertUpdateFindNegativeZero(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
insert bson.D
|
||||
update bson.D
|
||||
filter bson.D
|
||||
}{
|
||||
"Insert": {
|
||||
insert: bson.D{{"_id", "1"}, {"v", math.Copysign(0.0, -1)}},
|
||||
filter: bson.D{{"_id", "1"}},
|
||||
},
|
||||
"UpdateZeroMulNegative": {
|
||||
insert: bson.D{{"_id", "zero"}, {"v", int32(0)}},
|
||||
update: bson.D{{"$mul", bson.D{{"v", float64(-1)}}}},
|
||||
filter: bson.D{{"_id", "zero"}},
|
||||
},
|
||||
"UpdateNegativeMulZero": {
|
||||
insert: bson.D{{"_id", "negative"}, {"v", int64(-1)}},
|
||||
update: bson.D{{"$mul", bson.D{{"v", float64(0)}}}},
|
||||
filter: bson.D{{"_id", "negative"}},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := collection.InsertOne(ctx, tc.insert)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.update != nil {
|
||||
_, err = collection.UpdateOne(ctx, tc.filter, tc.update)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
var res bson.D
|
||||
err = collection.FindOne(ctx, tc.filter).Decode(&res)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := bson.D{
|
||||
{"_id", tc.filter[0].Value},
|
||||
{"v", math.Copysign(0.0, -1)},
|
||||
}
|
||||
|
||||
AssertEqualDocuments(t, expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertDocumentValidation(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t, shareddata.Scalars)
|
||||
|
||||
t.Run("Insert", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
doc bson.D
|
||||
}{
|
||||
"DollarSign": {
|
||||
doc: bson.D{{"$foo", "bar"}},
|
||||
},
|
||||
"DotSign": {
|
||||
doc: bson.D{{"foo.bar", "baz"}},
|
||||
},
|
||||
"Infinity": {
|
||||
doc: bson.D{{"foo", math.Inf(1)}},
|
||||
},
|
||||
"NegativeInfinity": {
|
||||
doc: bson.D{{"foo", math.Inf(-1)}},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := collection.InsertOne(ctx, tc.doc)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := collection.UpdateOne(ctx, bson.D{}, bson.D{{"$set", bson.D{{"foo", bson.D{{"bar.baz", "qaz"}}}}}}, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateProduceInfinity(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
_, err := collection.InsertOne(ctx, bson.D{{"_id", "number"}, {"v", int32(42)}})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = collection.UpdateOne(ctx, bson.D{{"_id", "number"}}, bson.D{{"$mul", bson.D{{"v", math.MaxFloat64}}}})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCreateCollectionDatabaseNameNonLatin(t *testing.T) {
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/420
|
||||
t.Parallel()
|
||||
|
||||
ctx, collection := setup.Setup(t)
|
||||
|
||||
dbName := "データベース"
|
||||
cName := testutil.CollectionName(t)
|
||||
|
||||
err := collection.Database().Client().Database(dbName).CreateCollection(ctx, cName)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = collection.Database().Client().Database(dbName).Drop(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -22,10 +22,10 @@ import (
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/iterator"
|
||||
"github.com/FerretDB/FerretDB/v2/internal/util/xiter"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/shareddata"
|
||||
)
|
||||
|
||||
func BenchmarkFind(b *testing.B) {
|
||||
@@ -91,8 +91,6 @@ func BenchmarkReplaceOne(b *testing.B) {
|
||||
})
|
||||
ctx, collection := s.Ctx, s.Collection
|
||||
|
||||
// use the last document by the natural order to make non-pushdown path slower
|
||||
|
||||
cursor, err := collection.Find(ctx, bson.D{})
|
||||
require.NoError(b, err)
|
||||
|
||||
@@ -135,8 +133,10 @@ func BenchmarkInsertMany(b *testing.B) {
|
||||
ctx, collection := setup.Setup(b)
|
||||
|
||||
for _, provider := range shareddata.AllBenchmarkProviders() {
|
||||
total, err := iterator.ConsumeCount(provider.NewIterator())
|
||||
require.NoError(b, err)
|
||||
var total int
|
||||
for range provider.NewIter() {
|
||||
total++
|
||||
}
|
||||
|
||||
var batchSizes []int
|
||||
for _, batchSize := range []int{1, 10, 100, 1000} {
|
||||
@@ -152,16 +152,7 @@ func BenchmarkInsertMany(b *testing.B) {
|
||||
for range b.N {
|
||||
require.NoError(b, collection.Drop(ctx))
|
||||
|
||||
iter := provider.NewIterator()
|
||||
|
||||
for {
|
||||
docs, err := iterator.ConsumeValuesN(iter, batchSize)
|
||||
require.NoError(b, err)
|
||||
|
||||
if docs == nil {
|
||||
break
|
||||
}
|
||||
|
||||
for docs := range xiter.Chunk(provider.NewIter(), batchSize) {
|
||||
insertDocs := make([]any, len(docs))
|
||||
for i := range insertDocs {
|
||||
insertDocs[i] = docs[i]
|
||||
@@ -169,7 +160,7 @@ func BenchmarkInsertMany(b *testing.B) {
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_, err = collection.InsertMany(ctx, insertDocs)
|
||||
_, err := collection.InsertMany(ctx, insertDocs)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.StopTimer()
|
||||
|
||||
@@ -15,320 +15,105 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"github.com/FerretDB/FerretDB/internal/util/must"
|
||||
"github.com/FerretDB/FerretDB/internal/util/testutil"
|
||||
|
||||
"github.com/FerretDB/FerretDB/integration/setup"
|
||||
"github.com/FerretDB/FerretDB/integration/shareddata"
|
||||
"github.com/FerretDB/FerretDB/v2/integration/setup"
|
||||
)
|
||||
|
||||
func TestCommandsAdministrationCompatCollStatsWithScale(t *testing.T) {
|
||||
func TestListCollectionsCompat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{
|
||||
Providers: []shareddata.Provider{shareddata.DocumentsDocuments},
|
||||
AddNonExistentCollection: true,
|
||||
})
|
||||
|
||||
ctx, targetCollection, compatCollection := s.Ctx, s.TargetCollections[0], s.CompatCollections[0]
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // for readability
|
||||
scale any
|
||||
resultType compatTestCaseResultType
|
||||
}{
|
||||
"scaleOne": {scale: int32(1)},
|
||||
"scaleBig": {scale: int64(1000)},
|
||||
"scaleMaxInt": {scale: math.MaxInt64},
|
||||
"scaleZero": {scale: int32(0), resultType: emptyResult},
|
||||
"scaleNegative": {scale: int32(-100), resultType: emptyResult},
|
||||
"scaleFloat": {scale: 2.8},
|
||||
"scaleFloatNegative": {scale: -2.8, resultType: emptyResult},
|
||||
"scaleMinFloat": {scale: -math.MaxFloat64, resultType: emptyResult},
|
||||
"scaleMaxFloat": {scale: math.MaxFloat64},
|
||||
"scaleString": {
|
||||
scale: "1",
|
||||
resultType: emptyResult,
|
||||
},
|
||||
"scaleObject": {
|
||||
scale: bson.D{{"a", 1}},
|
||||
resultType: emptyResult,
|
||||
},
|
||||
"scaleNull": {scale: nil},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var targetRes bson.D
|
||||
targetCommand := bson.D{{"collStats", targetCollection.Name()}, {"scale", tc.scale}}
|
||||
targetErr := targetCollection.Database().RunCommand(ctx, targetCommand).Decode(&targetRes)
|
||||
|
||||
var compatRes bson.D
|
||||
compatCommand := bson.D{{"collStats", compatCollection.Name()}, {"scale", tc.scale}}
|
||||
compatErr := compatCollection.Database().RunCommand(ctx, compatCommand).Decode(&compatRes)
|
||||
|
||||
if targetErr != nil {
|
||||
t.Logf("Target error: %v", targetErr)
|
||||
t.Logf("Compat error: %v", compatErr)
|
||||
|
||||
// error messages are intentionally not compared
|
||||
AssertMatchesCommandError(t, compatErr, targetErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, compatErr, "compat error; target returned no error")
|
||||
|
||||
targetDoc := ConvertDocument(t, targetRes)
|
||||
compatDoc := ConvertDocument(t, compatRes)
|
||||
|
||||
targetFactor := must.NotFail(targetDoc.Get("scaleFactor"))
|
||||
compatFactor := must.NotFail(compatDoc.Get("scaleFactor"))
|
||||
|
||||
assert.Equal(t, compatFactor, targetFactor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAdministrationCompatCollStatsCappedCollection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{
|
||||
Providers: []shareddata.Provider{},
|
||||
AddNonExistentCollection: true,
|
||||
})
|
||||
|
||||
targetDB := s.TargetCollections[0].Database()
|
||||
compatDB := s.CompatCollections[0].Database()
|
||||
|
||||
for name, tc := range map[string]struct {
|
||||
sizeInBytes int64
|
||||
maxDocuments int64
|
||||
|
||||
expectedSize int64
|
||||
}{
|
||||
"Size": {
|
||||
sizeInBytes: 256,
|
||||
expectedSize: 256,
|
||||
},
|
||||
"SizeRounded": {
|
||||
sizeInBytes: 1000,
|
||||
expectedSize: 1000,
|
||||
},
|
||||
"MaxDocuments": {
|
||||
sizeInBytes: 1,
|
||||
maxDocuments: 10,
|
||||
expectedSize: 1,
|
||||
},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cName := testutil.CollectionName(t) + name
|
||||
opts := options.CreateCollection().SetCapped(true).SetSizeInBytes(tc.sizeInBytes)
|
||||
|
||||
if tc.maxDocuments > 0 {
|
||||
opts.SetMaxDocuments(tc.maxDocuments)
|
||||
}
|
||||
|
||||
targetErr := targetDB.CreateCollection(s.Ctx, cName, opts)
|
||||
require.NoError(t, targetErr)
|
||||
|
||||
compatErr := compatDB.CreateCollection(s.Ctx, cName, opts)
|
||||
require.NoError(t, compatErr)
|
||||
|
||||
command := bson.D{{"collStats", cName}}
|
||||
|
||||
var targetRes bson.D
|
||||
targetErr = targetDB.RunCommand(s.Ctx, command).Decode(&targetRes)
|
||||
require.NoError(t, targetErr)
|
||||
|
||||
var compatRes bson.D
|
||||
compatErr = compatDB.RunCommand(s.Ctx, command).Decode(&compatRes)
|
||||
require.NoError(t, compatErr)
|
||||
|
||||
targetDoc := ConvertDocument(t, targetRes)
|
||||
compatDoc := ConvertDocument(t, compatRes)
|
||||
|
||||
assert.Equal(t, must.NotFail(compatDoc.Get("capped")), must.NotFail(targetDoc.Get("capped")))
|
||||
|
||||
// TODO https://github.com/FerretDB/FerretDB/issues/3582
|
||||
assert.EqualValues(t, tc.expectedSize, must.NotFail(targetDoc.Get("maxSize")))
|
||||
assert.EqualValues(t, must.NotFail(compatDoc.Get("maxSize")), must.NotFail(targetDoc.Get("maxSize")))
|
||||
assert.EqualValues(t, must.NotFail(compatDoc.Get("max")), must.NotFail(targetDoc.Get("max")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAdministrationCompatDBStatsWithScale(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{
|
||||
Providers: []shareddata.Provider{shareddata.DocumentsDocuments},
|
||||
AddNonExistentCollection: true,
|
||||
})
|
||||
|
||||
ctx, targetCollection, compatCollection := s.Ctx, s.TargetCollections[0], s.CompatCollections[0]
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // for readability
|
||||
scale any
|
||||
resultType compatTestCaseResultType
|
||||
}{
|
||||
"scaleOne": {scale: int32(1)},
|
||||
"scaleBig": {scale: int64(1000)},
|
||||
"scaleFloat": {scale: 2.8},
|
||||
"scaleNull": {scale: nil},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var targetRes bson.D
|
||||
targetCommand := bson.D{{"dbStats", int32(1)}, {"scale", tc.scale}}
|
||||
targetErr := targetCollection.Database().RunCommand(ctx, targetCommand).Decode(&targetRes)
|
||||
|
||||
var compatRes bson.D
|
||||
compatCommand := bson.D{{"dbStats", int32(1)}, {"scale", tc.scale}}
|
||||
compatErr := compatCollection.Database().RunCommand(ctx, compatCommand).Decode(&compatRes)
|
||||
|
||||
if targetErr != nil {
|
||||
t.Logf("Target error: %v", targetErr)
|
||||
t.Logf("Compat error: %v", compatErr)
|
||||
|
||||
// error messages are intentionally not compared
|
||||
AssertMatchesCommandError(t, compatErr, targetErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, compatErr, "compat error; target returned no error")
|
||||
|
||||
targetDoc := ConvertDocument(t, targetRes)
|
||||
compatDoc := ConvertDocument(t, compatRes)
|
||||
|
||||
targetFactor := must.NotFail(targetDoc.Get("scaleFactor"))
|
||||
compatFactor := must.NotFail(compatDoc.Get("scaleFactor"))
|
||||
|
||||
assert.Equal(t, compatFactor, targetFactor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAdministrationCompatDBStatsFreeStorage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{
|
||||
Providers: []shareddata.Provider{shareddata.DocumentsDocuments},
|
||||
AddNonExistentCollection: true,
|
||||
})
|
||||
|
||||
ctx, targetCollection, compatCollection := s.Ctx, s.TargetCollections[0], s.CompatCollections[0]
|
||||
|
||||
for name, tc := range map[string]struct { //nolint:vet // for readability
|
||||
command bson.D // required, command to run
|
||||
skip string // optional, skip test with a specified reason
|
||||
}{
|
||||
"Unset": {
|
||||
command: bson.D{{"dbStats", int32(1)}},
|
||||
},
|
||||
"Int32Zero": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(0)}},
|
||||
},
|
||||
"Int32One": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(1)}},
|
||||
},
|
||||
"Int32Negative": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(-1)}},
|
||||
},
|
||||
"True": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", true}},
|
||||
},
|
||||
"False": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", false}},
|
||||
},
|
||||
"Nil": {
|
||||
command: bson.D{{"dbStats", int32(1)}, {"freeStorage", nil}},
|
||||
},
|
||||
} {
|
||||
name, tc := name, tc
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var targetRes bson.D
|
||||
targetErr := targetCollection.Database().RunCommand(ctx, tc.command).Decode(&targetRes)
|
||||
|
||||
var compatRes bson.D
|
||||
compatErr := compatCollection.Database().RunCommand(ctx, tc.command).Decode(&compatRes)
|
||||
|
||||
if targetErr != nil {
|
||||
t.Logf("Target error: %v", targetErr)
|
||||
t.Logf("Compat error: %v", compatErr)
|
||||
|
||||
// error messages are intentionally not compared
|
||||
AssertMatchesCommandError(t, compatErr, targetErr)
|
||||
|
||||
return
|
||||
}
|
||||
require.NoError(t, compatErr, "compat error; target returned no error")
|
||||
|
||||
targetDoc := ConvertDocument(t, targetRes)
|
||||
compatDoc := ConvertDocument(t, compatRes)
|
||||
|
||||
assert.Equal(t, compatDoc.Has("freeStorageSize"), targetDoc.Has("freeStorageSize"))
|
||||
assert.Equal(t, compatDoc.Has("totalFreeStorageSize"), targetDoc.Has("totalFreeStorageSize"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandsAdministrationCompatListCollections(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, targetCollections, compatCollections := setup.SetupCompat(t)
|
||||
|
||||
require.Greater(t, len(targetCollections), 2)
|
||||
filterNames := make(bson.A, len(targetCollections))
|
||||
for i, n := range targetCollections {
|
||||
filterNames[i] = n.Name()
|
||||
}
|
||||
|
||||
// We should remove shuffle there once it is implemented in the setup.
|
||||
// TODO https://github.com/FerretDB/FerretDB-DocumentDB/issues/825
|
||||
|
||||
rand.Shuffle(len(filterNames), func(i, j int) { filterNames[i], filterNames[j] = filterNames[j], filterNames[i] })
|
||||
filterNames = filterNames[:len(filterNames)-1]
|
||||
require.NotEmpty(t, filterNames)
|
||||
|
||||
filter := bson.D{{
|
||||
Key: "name", Value: bson.D{{
|
||||
Key: "$in", Value: bson.A{
|
||||
targetCollections[0].Name(),
|
||||
targetCollections[len(targetCollections)-1].Name(),
|
||||
},
|
||||
"name", bson.D{{
|
||||
"$in", filterNames,
|
||||
}},
|
||||
}}
|
||||
|
||||
target, err := targetCollections[0].Database().ListCollections(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
var targetRes []bson.D
|
||||
err = target.All(ctx, &targetRes)
|
||||
require.NoError(t, err)
|
||||
docTarget := ConvertDocument(t, targetRes[0])
|
||||
|
||||
compat, err := compatCollections[0].Database().ListCollections(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
defer compat.Close(ctx)
|
||||
|
||||
var compatRes []bson.D
|
||||
err = compat.All(ctx, &compatRes)
|
||||
require.NoError(t, err)
|
||||
docCompat := ConvertDocument(t, compatRes[0])
|
||||
|
||||
compatNames := make([]string, len(compatRes))
|
||||
for i, doc := range compatRes {
|
||||
compatNames[i] = doc.Map()["name"].(string)
|
||||
}
|
||||
|
||||
require.True(t, slices.IsSorted(compatNames), "compat collections are not sorted")
|
||||
|
||||
target, err := targetCollections[0].Database().ListCollections(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
defer target.Close(ctx)
|
||||
|
||||
var targetRes []bson.D
|
||||
err = target.All(ctx, &targetRes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, target.RemainingBatchLength(), compat.RemainingBatchLength())
|
||||
|
||||
// Check idIndex
|
||||
targetIDIndex := must.NotFail(docTarget.Get("idIndex"))
|
||||
compatIDIndex := must.NotFail(docCompat.Get("idIndex"))
|
||||
comparable := func(res []bson.D) []bson.D {
|
||||
var resComparable []bson.D
|
||||
|
||||
assert.NotEmpty(t, targetIDIndex)
|
||||
assert.Equal(t, targetIDIndex, compatIDIndex)
|
||||
for _, doc := range res {
|
||||
var docComparable bson.D
|
||||
|
||||
for _, field := range doc {
|
||||
switch field.Key {
|
||||
case "info":
|
||||
info, ok := field.Value.(bson.D)
|
||||
require.True(t, ok)
|
||||
|
||||
var infoComparable bson.D
|
||||
|
||||
for _, infoField := range info {
|
||||
switch infoField.Key {
|
||||
case "uuid":
|
||||
uuid, uuidOk := infoField.Value.(primitive.Binary)
|
||||
require.True(t, uuidOk)
|
||||
assert.Equal(t, bson.TypeBinaryUUID, uuid.Subtype)
|
||||
assert.Len(t, uuid.Data, 16)
|
||||
infoComparable = append(infoComparable, bson.E{Key: infoField.Key, Value: primitive.Binary{}})
|
||||
default:
|
||||
infoComparable = append(infoComparable, infoField)
|
||||
}
|
||||
}
|
||||
|
||||
docComparable = append(docComparable, bson.E{Key: field.Key, Value: infoComparable})
|
||||
|
||||
default:
|
||||
docComparable = append(docComparable, field)
|
||||
}
|
||||
}
|
||||
|
||||
resComparable = append(resComparable, docComparable)
|
||||
}
|
||||
|
||||
return resComparable
|
||||
}
|
||||
|
||||
AssertEqualDocumentsSlice(t, comparable(compatRes), comparable(targetRes))
|
||||
}
|
||||
Loaded 100 of 714 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user